【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,
//启动关闭摄像头
};