驱动学习-日常笔记day10

【1】块设备
块设备的特点:
按照扇区来访问,访问的单位512字节
它可以顺序访问,也可以无序访问。

块设备的硬件知识:
比如一个移动硬盘:
磁头:有多少个盘面
磁道:一个面内有多少环
扇区:一个环内有多少个扇区,一个扇区512字节

磁盘的数据的读取:
磁盘数据的读取不会按照存储顺序来读取,因为磁头它是机械结构,
通过旋转来访问数据,如果按照数据来访问,需要反复的切换这个
物理结构,比较浪费时间。所以磁盘在访问的时候采用电梯优化的算法
来完成。即一次将一个盘面上的数据全部读取到,然后切换物理结构,
在读取下面的数据。将所有的数据读取完之后,进行数据的排序,排序
之后进行操作。


页 4K                    段 :可以包含多个block
block 512字节 1K 2K 4K   扇区 512字节

【2】块设备的框架结构
user:
open read write close (一切皆文件)
------------------|(io请求)----------------------
kernel:|VFS:(struct block_device)
|ext2 ext3 ext4 yaffs jiffs
|
|通过上述的文件系统就为将io请求转化成块
|请求bio(block input out put),Linux内核就
|把物理地址上连续的bio请求合成一个request。
|request会被放到一个队列中。
|-----------------------------------------
|块设备驱动:struct gendisk
| 1.分配的对象
| 2.对象的初始化,初始化一个队列
| 3.硬盘的硬件的操作
| 4.注册、注销

hardware : 硬盘

【3】块设备驱动的结构和函数
1.块设备结构体
struct gendisk {
int major; //主设备号
int first_minor; //次设备号的起始值
int minors; //设备的个数
char disk_name[DISK_NAME_LEN];//块设备的名字
struct disk_part_tbl *part_tbl; //硬盘分区表
struct hd_struct part0; //硬盘的一个分区
const struct block_device_operations *fops;
//块设备的操作方法结构体
struct request_queue *queue; //请求队列
void *private_data; //私有数据
};
2.分区结构体
struct hd_struct {
sector_t start_sect; //起始的扇区
sector_t nr_sects; //扇区的个数
sector_t alignment_offset; //对齐的方式
int partno; //分区号
};

3.操作方法结构体
struct block_device_operations {
	int (*open) (struct block_device *, fmode_t);
	int (*release) (struct gendisk *, fmode_t);
	int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
	int (*getgeo)(struct block_device *, struct hd_geometry *);
	//设置磁盘有多少个磁头,有多少个磁道,有多少扇区
};


4.队列的操作的方法
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
功能:初始化一个队列
参数:
	@rfn  :队列处理函数
	typedef void (request_fn_proc) (struct request_queue *q);
	//这个是队列处理函数的原型,在这个函数中要进行读写操作
	@lock :自旋锁
返回值:成功返回初始化好的队列的指针
		失败返回NULL
		
		
struct request *blk_fetch_request(struct request_queue *q)
功能:从队列中取出一个请求
参数:
	@q :队列
返回值:成功返回request结构体指针
		失败返回NULL

void blk_cleanup_queue(struct request_queue *q)
功能:清除队列
参数:
	@q:被清除的队列
返回值:无

5.关于request的操作
 sector_t blk_rq_pos(const struct request *rq)
 功能:从请求中获取本次操作的起始的函数

 unsigned int blk_rq_cur_sectors(const struct request *rq)
 功能:获取本次想读写的扇区的个数
 
 int blk_rq_cur_bytes(const struct request *rq)
 功能:获取本次想读写的字节的个数

 rq_data_dir(rq)
 功能:从request中获取本次是读还是写
 rq_data_dir(rq) == READ  读
 rq_data_dir(rq) == WRITE 写

bool __blk_end_request_cur(struct request *rq, 0)
功能:判断request的读写是否处理完了
参数:
	@rq :请求队列
返回值:真:表示数据没有处理完
		假:数据处理完成了
	
2.gendisk内存分配并初始化的函数
	struct gendisk *alloc_disk(int minors)
	功能:分配内存并初始一些内容
	参数:
		@minors :设备的个数
		
2.申请块设备的设备号
int register_blkdev(unsigned int major, const char *name)
功能:申请块设备的设备号
参数:
	@major :如果填写的是大于0的值静态指定设备号
			如果填写的是0表示动态申请设备号
	@name  :cat /proc/devices		
返回值:
	major > 0   成功返回0,失败返回错误码
	major = 0   成功返回主设备号,失败返回小于等于0的值
	
	
void unregister_blkdev(unsigned int major, const char *name)
功能:失败块设备的设备号
参数:
	@major :主设备号
	@name  :名字
返回值:无

3.块设备驱动的注册/注销
void add_disk(struct gendisk *disk)
功能:注册gendisk
参数:
	@disk :gendisk对象的地址
void del_gendisk(struct gendisk *disk)
功能:注销gendisk
参数:
	@disk :gendisk对象的地址


块设备驱动的测试:
1.安装驱动
	sudo  insmod  mydisk.ko 
2.查看
	cat /proc/devices
	ls /dev/mydisk
	sudo fdisk -l
3.分区
	sudo fdisk /dev/mydisk
	:m  --->打印帮助信息
	:n  --->新建一个分区
	:p  --->打印分区
	:w  --->保存退出
	:q  --->退出
	
4.格式化
	sudo mkfs.ext3 /dev/mydisk1
5.将磁盘挂载到一个目录下
	sudo mount /dev/mydisk1 ~/udisk
	
	在udisk中存放文件和目录
6.取消挂载
	sudo umount ~/udisk
	
7.将刚才写进磁盘的数据给读出来
	cat /dev/mydisk1 > mydisk_file.bin
	
8.查看.bin中是否有文件
	sudo mount -o loop mydisk_file.bin ~/udisk
	在udisk中可以看到刚才写进入的文件和目录
	表示块设备驱动是成功的。

内存分配的函数:
虚拟内存:
1.地址
物理地址:在datasheet中能够查到的地址称之为物理地址,实际设备的操作地址;
虚拟地址、线性地址:在操作系统程序员能够操作的地址称之为虚拟地址;
逻辑地址:将程序进行反汇编之后,其中能够看到的地址称之为逻辑地址;

2.内存管理
	段式管理
	页式管理
	
3.内存映射关系
	4g------------------------------------
                  4k
	 -----------------------------------
		       固定内存映射区 4m-4k
     -----------------------------------
	           高端内存映射区 4m(alloc_page)
	 ------------------------------------
				NULL 8K(保护)
	 ------------------------------------vmalloc  end
			 vmalloc内存区120m-8m-8k(低端或者高端内存)
	 ------------------------------------vmalloc  start
				vmalloc offset 8m
	 ------------------------------------
				物理内存映射区896M(kmalloc get_free_page低端内存)
   3g------------------------------------  物理内存 3g 偏移   4k + 3g
					用户空间
   0g------------------------------------
   
	
4.linux内存分配函数
	void *kmalloc(size_t s, gfp_t gfp)
	功能:分配对应的虚拟内存
	参数:size:分配内存区的大小(2的次幂,最大是128K,连续)
		  flags:内存分配标志
			  GFP_KERNEL:内核可能被休眠,进程上下文,不能用于中断上下文中
			  GFP_ATOMIC:处理紧急的事务,用在中断上下文
		  
	返回值:对应虚拟地址
	特点:最大128k   , 分配虚拟地址,其虚拟地址空间连续,物理地址空间也是连续
	类似函数:kzalloc:kmalloc+memset(buf,0,sizeof(buf));


	void kfree(const void *x)
	功能:释放对应的虚拟内存
	参数:x:虚拟内存的起始地址
	返回值:无
	
	void *vmalloc(unsigned long size)
	功能:分配对应的虚拟内存
	参数:size:分配内存区的大小
	返回值:对应虚拟地址
	特点:分配虚拟地址,其虚拟地址空间连续,但是物理地址空间不一定连续
	
	void vfree(const void *addr)
	功能:释放对应的虚拟内存
	参数:addr:虚拟内存区的首地址
	返回值:无
	
	unsigned long __get_free_page(gfp_t gfp)
	功能:分配一个物理页
	参数:
		@gfp
		GFP_KERNEL:内核可能被休眠,进程上下文,不能用于中断上下文中
		GFP_ATOMIC:处理紧急的事务,用在中断上下文
	void free_page(unsigned long addr)
	
	unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
	功能:分配多个物理页
	参数:
		@gfp_mask:
		GFP_KERNEL:内核可能被休眠,进程上下文,不能用于中断上下文中
		GFP_ATOMIC:处理紧急的事务,用在中断上下文
		@order:填写的是想分配内存的次值n=get_order(57600)
			57600 = 2^n
			
	void free_pages(unsigned long addr, unsigned long order)

【1】摄像头驱动
V4L2:video for linux 2:它是linux内核中摄像头驱动的一个框架,它会为
应用提供一个/dev/video0。
1.video:摄像头驱动
2.vbi :场消隐的视频信号(有线电视)
3.radio:无线电设备(收音机)

虚拟摄像头:vivi
linux lxr :在线查看linux内核源码的网站:
https://lxr.missinglinkelectronics.com/linux/

1.下载虚拟摄像头驱动
	在网站中找到3.5内核的vivi.c的代码
	点击下载:vivi.c

2.使用Makefile对vivi.c进行编译
	编译完成之后生成vivi.ko

3.安装驱动
	insmod: error inserting 'vivi.ko': -1 Unknown symbol in module
	
	insmod安装驱动的时候不会安装依赖文件
	modprobe:在安装驱动的时候会检查依赖
	sudo modprobe -i vivi
	sudo modprobe -r vivi

	在安装成功之后就会产生一个/dev/video0的设备节点。
	
4.安装应用程序来读取摄像头的数据
	 sudo apt-get install xawtv
	 sudo apt-get install cheese

【2】摄像头驱动
1.摄像头的对象
struct video_device *vfd;
vfd = video_device_alloc();

2.初始化摄像头的对象
	static struct video_device vfd = {                                                       
		.name       = "vivi",
		.fops       = &vivi_fops,  //v4l2的操作方法结构体
		.ioctl_ops  = &vivi_ioctl_ops, //v4l2的ioctl函数
		.release    = video_device_release,
	};

3.注册
	video_register_device(vfd, VFL_TYPE_GRABBER, video_nr);   
	完成摄像头设备的初始化
		__video_register_device(vdev, type, nr, 1, vdev->fops->owner);
	   switch (type) {
		case VFL_TYPE_GRABBER:                                                                         
			name_base = "video";  //注册的是摄像头设备驱动
			break;
		case VFL_TYPE_VBI:
			name_base = "vbi";   //场消隐设备驱动
			break;
		case VFL_TYPE_RADIO:     //无线电设备的驱动
			name_base = "radio";
			break;
		}

	  switch (type) {
		case VFL_TYPE_GRABBER:
			minor_offset = 0;
			minor_cnt = 64;
			break;
		case VFL_TYPE_RADIO:
			minor_offset = 64;                                                                         
			minor_cnt = 64;
			break;
		case VFL_TYPE_VBI:
			minor_offset = 224;
			minor_cnt = 32;
			break;
		}

	字符设备驱动的注册:
	static const struct file_operations v4l2_fops = {
		.read = v4l2_read,
		.write = v4l2_write,
		.open = v4l2_open,
		.mmap = v4l2_mmap,
		.unlocked_ioctl = v4l2_ioctl,
		.release = v4l2_release,
		.poll = v4l2_poll,
	};   
	
	vdev->cdev = cdev_alloc();
	vdev->cdev->ops = &v4l2_fops;
	ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
	#define VIDEO_MAJOR 81  摄像头的主设备号是81
	//创建了设备节点相当于class_create  device_create
	   vdev->dev.class = &video_class;
		vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
		if (vdev->parent)
			vdev->dev.parent = vdev->parent;
		dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
		ret = device_register(&vdev->dev);   

user:
open("/dev/video0") read write ioctl close
----------|------------------------------------------------
|
字符设备驱动:v4l2_fops
static const struct file_operations v4l2_fops = {
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
.release = v4l2_release,
.poll = v4l2_poll,
;
上述的open read write ioctl …会调用v4l2_file_operations
************************************************************
static const struct v4l2_file_operations vivi_fops = {
.open = v4l2_fh_open,
.release = vivi_close,
.read = vivi_read,
.poll = vivi_poll,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.mmap = vivi_mmap,
};

上述的ioctl函数会调用v4l2_ioctl_ops 
***************************************************************
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
.vidioc_querycap      = vidioc_querycap,
//查询设备的类型是否是摄像头设备

.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
//枚举,获取,尝试,设置摄像头格式

.vidioc_reqbufs       = vidioc_reqbufs,
.vidioc_querybuf      = vidioc_querybuf,
.vidioc_qbuf          = vidioc_qbuf,
.vidioc_dqbuf         = vidioc_dqbuf, 
//申请,查询,存放,取出buf等操作

.vidioc_streamon      = vidioc_streamon,
.vidioc_streamoff     = vidioc_streamoff,
//启动关闭摄像头
};

猜你喜欢

转载自blog.csdn.net/weixin_48430195/article/details/108681530