基于Linux内核的驱动开发
1 中断下半部
一般来说,操作系统中的中断越短越好,如果系统中的中断处理时间过长,可以
将中断处理过程分成两部分来处理。
第一部分,专门接收和响应中断请求(登记中断)--》上半部-->handler
第二部分,专门来处理中断的耗时业务逻辑(处理耗时操作)--》下半部
Linux系统中的中断处理也是分成上半部和下半部来完成的。下半部的处理方法有:
tasklet 工作队列 内核定时器
1.1 tasklet--》小“片”任务
1 声明一个 tasklet
DECLARE_TASKLET(name, func, data)
name:小任务的名称
func:小任务对应的处理函数
void (*func)(unsigned long data);
data:传给func函数的入参
2 调度小任务---》handler
tasklet_schedule(&name)
static inline void tasklet_schedule(struct tasklet_struct *t)
作用:调度小任务
*t:指向小任务名称的指针
1.2 工作队列
是指在中断处理中可以把耗时的操作推迟执行,可以把耗时的操作交到工作
队列中等待被执行,内核中有一个线程events会去工作队列中找等待被执行
的工作,然后执行这个等待的工作(工作:中断处理程序中耗时的那部分操作)
1 定义一个工作队列
struct work_struct my_wq;
2 初始化工作队列
INIT_WORK(_work, _func)
_work:指向工作队列的指针
_func:工作队列对应的处理函数
typedef void (*work_func_t)(struct work_struct *work);
3 调度工作队列
schedule_work(&my_wq);
1.3 内核定时器
1 struct time_list mytimer;定义一个内核定时器
2 helloprobe:
init_timer(mytimer);初始化内核定时器
mytimer.function=my_func;-->用来处理耗时的操作
mytimer.expires=时间值--》表示超时时间
add_timer(&mytimer)-->启动定时
2 块设备驱动
2.1 块设备以及相关术语
块设备:可以从设备的任意位置读取一定长度数据的设备
常见块设备:硬盘 磁盘 光盘 U盘 sd卡 tf卡。。。
硬件种类:机械硬盘
固态硬盘
混合硬盘
磁盘概念:
磁头:是磁臂顶端用来指向硬盘的读写数据的磁针
扇区:是读写数据的最小单位512byte
柱面:多张累加的磁盘上同一半径的磁道形成的面
磁道:磁头画的同心圆就是磁道,用来存放数据
硬盘容量:磁头数*柱面数*每个磁道包含的扇区个数*512byte
磁头:8
扇区:16
柱面:128
2.2 模拟创建一个硬盘,并实现其驱动
init流程:--》HelloModule
1 创建一个块设备号
int register_blkdev(unsigned int major, const char *name)
作用:创建块设备号
major:主设备号。major>0,静态创建块设备号
major=0,动态创建块设备号
*name:块设备名称 系统中唯一,不能重复
返回值:成功 设备号
失败 负数
2 创建并初始化请求队列--》用户对设备的读写请求
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
作用:初始化一个请求队列(用自旋锁来控制对列的访问方向)
*rfn:请求处理函数
typedef void (request_fn_proc) (struct request_queue *q)
*lock:指向自旋锁的指针
返回值:指向请求队列的指针
3 分配一个gendisk结构体
struct gendisk *alloc_disk(int minors)
作用:分配一块存放块设备结构体的内存
minors:次设备的数量(最大分区数)
返回值:指向块设备的结构指针
用struct gendisk来描述一个块设备
struct gendisk {
int major;--》主设备号
int first_minor;--》第一个次设备号
int minors;--》次设备的个数
char disk_name[DISK_NAME_LEN]; --》块设备名称
const struct block_device_operations *fops;--》块设备操作方法集
struct request_queue *queue;--》IO请求队列
。。。
}
4 设置gendisk结构体成员
如上
5 为磁盘分配内存
void *vmalloc(unsigned long size)
size:分配的内存大小
返回值:返回指向内存的指针
6 设置磁盘容量
static inline void set_capacity(struct gendisk *disk, sector_t size)
*disk:指向块设备的指针
size:总的扇区数
7 添加磁盘到内核中
void add_disk(struct gendisk *disk)
*disk:指向块设备的指针
exit流程:--->HelloExit
1 释放磁盘扇区缓存
2 释放gendisk结构体
void del_gendisk(struct gendisk *disk)
*disk:指向块设备结构体的指针
3 清除内存中的请求队列
void blk_cleanup_queue(struct request_queue *q)
*q:指向请求队列的指针
4 删除块设备号
void unregister_blkdev(unsigned int major, const char *name)
major:设备号
*name:设备名称
struct hd_geometry {--》用来描述磁盘的几何信息
unsigned char heads;--》磁头数
unsigned char sectors;--》每个磁道包含的扇区个数
unsigned short cylinders;--》柱面数
unsigned long start;--》起始地址
};
实现读写的请求处理函数:
void RequestFunc(struct request_queue *p)
1 从请求对列中获取一个请求
struct request *blk_fetch_request(struct request_queue *q)
*q:指向请求队列的指针
返回值:从请求队列中获取到的一个请求
2 获取当前请求的位置(在哪里读写)
static inline sector_t blk_rq_pos(const struct request *rq)
*rq:指向请求的指针
返回值:扇区的位置
blk_rq_pos*512=具体的字节位置
3 获取从当前位置要读/写多少个字节?
static inline unsigned int blk_rq_cur_sectors(const struct request *rq)
作用:获取该请求需要从当前位置获取多少扇区
*rq:指向请求的指针
返回值:扇区的个数
blk_rq_cur_sectors*512=具体的字节大小
4 判断请求的方向?
rq_data_dir(rq)
*rq:指向请求的指针
返回值:读 READ 写 WRITE
5 根据请求的方向读写数据
read:硬盘数据---》req->buffer
memcpy(req->buffer,g_Data+offset,nbytes)
write:req->buffer--》硬盘
memcpy(g_Data+offset,req->buffer,nbytes)
6 判断当前请求是否处理完毕?
bool __blk_end_request_cur(struct request *rq, int error)
*rq:指向请求的指针
error:0
返回值:结束 false;未结束 true
测试:
虚拟机:
1 hello.c--->交叉编译--》hello.ko
2 cp hello.ko /source4/rootfs
开发板:
1 启动开发板 配置ip相关项 配置bootarg为nfs
2 重启开发板
root@farsight#insmod hello.ko
ls -l /dev/hellodisk*-->查看是否有模拟的块设备
分区:
fdisk /dev/hellodisk -u(按照扇区划分)
m:help
n:创建分区
p-->创建主分区 16~5000
e--->创建扩展分区 5001~10000
w:保存并退出
q:只退出不保存
p:打印分区表信息
ls -l /dev/hellodisk*--->查看分区是否OK?
格式化要使用的分区:
mkfs.ext2 /dev/hellodisk1
挂载分区到mnt目录下并测试读写
mount -t ext2 /dev/hellodisk1 /mnt
cd /mnt
vi 1.txt---》创建并写入内容
dmesg |tail-->查看读写驱动有没有被调用
vi 1.txt-->打开