LDD3源码学习笔记之scull_main
/*scull.h*/
/*main.c*/
/*=========================================*/
/*头文件相关定义*/
1.定义scull_dev结构体用来描述scull设备储存结构
/*主函数流程分析*/
1.在程序的开始处使用module_param定义模块参数
2.初始化模块module_init(scull_init_module){
scull_init_module分析:
(1)获得内核识别的设备号若动态分配则调用alloc_chrdev_region,若已知则调用register_chrdev_region
(2)使用kmalloc动态的给scull_dev设备结构体在普通内核内存空间分配内存空间,并使用memset初始化为0
(3)初始化每个设备的访问区块(即内存区块),设定量子和量子集,互斥锁,调用scull_setup_cdev向内核注册这个char device
scull_setup_cdev注册char设备的流程:
a.调用MKDEV由实参生成具体设备的设备号
b.初始化cdev,主要是指定其ops cdev_init(&dev->cdev, &scull_fops).dev->cdev在设备结构体中定义.
关于定义设备的file_operations fops.设备的访问等同与文件,所以这里需要具体指定打开文件时候的操作.
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
...};/*注意这里标记化结构体初始化的语法*/
c.cdev的初始化,指定所有者 dev->cdev.owner = THIS_MODULE;
d.注册cdev cdev_add (&dev->cdev, devno, 1);
(4)初始化其他相关友好的设备/*这里是指pipe和有open control机制的设备实现,本次不学习*/
(5)调用scull_create_proc建立通过proc文件的debug机制需要的proc文件
/*注意:这里使用goto的方式对每步骤出现的错误进行恢复*/
调用scull_cleanup_module进行上述步骤的错误退出.
}
3.退出并注销模块module_exit(scull_cleanup_module){
(1).获得设备号MKDEV
(2).调用scull_trim来清除dev设备文件,对在初始化分配内存的所有步骤都作kfree释放操作,对设备结构体数组值重新设定.
/*注意scull_trim必须被拥有旗帜的函数所调用,而且其中所以DATA区域在释放后依然赋值为NULL是从安全角度出发*/
(3).删除已经注册的char设备:cdev_del
(4).释放设备结构体
(5).调用scull_remove_proc删除初始化阶段创立的proc文件调试机制
(6).调用unregister_chrdev_region注销注册的设备号
(7).注销其他相关友好的设备
}
4.关于对设备读写等的file-ops函数的定义(){
(1)open(){
原型:int (*open)(struct inode *inode, struct file *filp)
a.通过indode->i_cdev(其本身指向scull_dev的cdev)获得scull_dev的入口指针/*识别需要被打开的设备*/
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
b.将获得的dev保存到filp->private_data filp->private_data = dev
c.如果是以只读模式打开则调用scull_trim()截短设备为0
filp->f_flags & O_ACCMODE) == O_WRONLY,判读其打开模式
对访问区域加锁down_interruptible(&dev->sem)
调用scull_trim(dev)
解锁up(&dev->sem);
}
(2)release(){/*作用和open相反*/
原型:int (*release)(struct inode *inode, struct file *filp)
}
(3)read(){
原型 ssize_t read(struct file *filp, char __user *buf, size_t count,loff_t *f_pos)
a.通过filp->private_data获得已经保存在其中的设备结构体入口指针:struct scull_dev *dev = filp->private_data;
b.初始化一些结构体和变量.比如scull_qset结构体,量子和量子集等等.
c.加锁down_interruptible(&dev->sem)
d.判读off_t *f_pos参数是否超越了设备存储的范围:*f_pos >= dev->size
修正能够返回数据的长度(*f_pos + count > dev->size) && (count = dev->size - *f_pos)
e.从f_pos获得find listitem, qset index, and offset in the quantum
f.调用scull_follow依据e步的item获得其设备结构体中的入口地址(即需要的qset的地址),即通过qset->next寻找item-1次即可,如果qset还没有被实体化,那么就调用kmalloc,memset即可.
找到后,判读qset地址,其指向的量子集,和量子集中的量子的正确性.
g./*注意:我们在这里约定每次读和写只操作一个量子*/
所以要根据实际量子中的off和量子长度再次调整count值
h.调用copy_to_user将内核数据复制到用户区域.
i.f_pos处理加上实际读出来的count
j.错误和结束:解锁即可.
}
(4)write(){
原型 ssize_t write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
基本同read,只是h步骤使用copy_from_user.
/*注意如果内核访问用户区域,由于user域存在分页机制,进程有可能被休眠以等待不再当前页中的数据,所以要求copy_from_user函数是可重入的.*/
}
(5)llseek(){
原型:(*llseek)(struct file *filp, loff_t off, int whence)
a.通过filp->private_data获得已经保存在其中的设备结构体入口指针
b.根据llseek的第二个参数,返回不同的地址
c.指定filp->f_pos为b步的返回值
}
5.iotcl的实现(){
具体间收获
}
}
/*=========================================*/
/*收获*/
/*=========================================*/
1.如何用宏定义来实现DEBUG调试(){
程序中:
#undef PDEBUG /* undef it, just in case */
#ifdef SCULL_DEBUG
# ifdef __KERNEL__ /* This one if debugging is on, and kernel space */
# define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args)
# else /* This one for user space */
# define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
# endif
#else /* not debugging: nothing */
# define PDEBUG(fmt, args...)
#endif
编译时候:
gcc -DSCULL_DEBUG/*即define SCULL_DEBUG*/
}
/*--------------------------------*/
2.ioctl向驱动程序发出请求在驱动中的实现(){
1.定义ioctl的cmd参数
#define SCULL_IOC_MAGIC 'k'/* Use 'k' as magic number */
#define SCULL_XXX0 _IO(SCULL_IOC_MAGIC, 0)/*无参数的,即无数据传递*/
#define SCULL_XXX1 _IOW(SCULL_IOC_MAGIC, 1,int)/*无参数的,即无数据传递*/
#define SCULL_XXX2 _IOR(SCULL_IOC_MAGIC, 2,int)/*无参数的,即无数据传递*/
2.驱动程序中如何实现ioctl
原型:int (*ioctl)(struct inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)
a.判断cmd类型和个数是否正确,调用_IOC_TYPE,_IOC_NR
b.如果有数据通过指针传递,首先检查传递指针是否有效:access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd))
调用_IOC_DIR,_IOC_SIZE分别得到cmd的方向和参数的长度
/*注意如果cmd是读,那么要检查user是否能被内核写,反之则反之*/
c.经典的switch结构,根据cmd分别做分别的事情.
其中使用指针传递参数的实现方法:
从用户空间传递到内核:__get_user(内核变量名,(int __user *)参数),
反之则用__put_user(内核变量名,(int __user *)参数)
}
/*--------------------------------*/
3.设备访问区域使用semaphore实现mutual exclusion的流程(){
相关头文件asm/semaphore.h
1.定义旗帜变量与设备结构体中:xxx_dev:struct semaphore sem
2.在设备访问区块初始时候,初始化旗帜变量init_MUTEX(xxx_dev.sem)
3.具体运用:
对访问区域加锁down_interruptible(&xxx_dev->sem)
解锁up(&xxx_dev->sem);
}
/*--------------------------------*/
4.如何建立通过proc的debug机制(){
方法一:通过一般的/proc文件
1.创建对/proc文件可读的方法函数
这个函数虽然是自己定义,但是有共同的type,如下
int (*read_proc)(char *page, char **start, off_t offset, int count, int *eof, void *data);也就是说要把返回的信息写在kernel中的buf区域
然后可以根据自己的需要写对应的读函数,本例中是int scull_read_procmem(char *buf, char **start, off_t offset,int count, int *eof, void *data)
2.将上步骤的函数和具体的.proc文件关联
定义文件相关结构体:struct proc_dir_entry *entry;
create_proc_read_entry,创建文件并使之关联read函数
方法二:通过seq_file文件接口
0.根据seq_file ops的原型定义自己的函数,这些主要用于open时寻找iterator
scull_seq_start,scull_seq_next,scull_seq_stop,scull_seq_show
/*在这些函数的创建中尽量使用seq对应的一些函数如seq_printf,seq_putc等*/
1.创建seq文件的ops
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,...};
2.创建file-ops需要的函数open,同时也注册好上述seq的ops
static int scull_proc_open(struct inode *inode, struct file *file)
{return seq_open(file, &scull_seq_ops);}/*由于seq文件(iterator)需要特别的寻找方法,即seq ops提供的*/
3.创建文件ops结构体,指定对该文件的相关操作的函数
static struct file_operations scull_proc_ops = {
.owner = THIS_MODULE,
.open = scull_proc_open,/*使用自己定的open*/
.read = seq_read,...};/*使用seq的函数*/
4.创建真实的文件,并指定文件的ops
entry = create_proc_entry("scullseq", 0, NULL);
if (entry)
entry->proc_fops = &scull_proc_ops;
关于注销对应文件两种方法的做法是一样的
remove_proc_entry("name", NULL /* parent dir */);
/*注意对设备有互斥锁部分访问时候的down up机制的运用.*/
}
/*=========================================*/
/*遗留*/
/*=========================================*/
1. 主函数分析 2->(4)步骤涉及用pipe实现和有open control机制,本次暂不学习
类似的有3->(7)
/*=========================================*/
文章出处: