字符设备驱动复习:
1、问题:构建用于驱动开发环境的最小根文件系统时,动态链接了C函数库,但是驱动程序用的是内核导出的函数,而测试程序是在服务器编译的,
用的是服务器的编译工具和C函数库,所以,最小根文件系统的动态C函数库有什么作用呢?什么时候使用呢?
2、fops结构的成员.release代表的(?)函数的调用的条件和场景?
摘自LDD3:内核在进程退出的时候,通过在内部使用close系统调用自动关闭所有相关的文件。
也就是说,开发者自己基本不需要手动执行.release而只需要把相关的close函数定义好即可?
3、字符设备驱动程序板块--函数大略参考(摘自LDD3>>字符设备驱动程序):
----------------------------------------------------------------------------------------------
#include <linux/types.h>
dev_t
dev_t 是用来在内核里代表设备号的类型.
int MAJOR(dev_t dev);
int MINOR(dev_t dev);
从设备编号中抽取主次编号的宏.
dev_t MKDEV(unsigned int major, unsigned int minor);
从主次编号来建立 dev_t 数据项的宏定义.
#include <linux/fs.h>
"文件系统"头文件是编写设备驱动需要的头文件. 许多重要的函数和数据结构在此定义.
int register_chrdev_region(dev_t first, unsigned int count, char *name)
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
void unregister_chrdev_region(dev_t first, unsigned int count);
允许驱动分配和释放设备编号的范围的函数. register_chrdev_region 应当用在事先知道需要的主编号时;
对于动态分配, 使用alloc_chrdev_region 代替.
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
老的( 2.6 之前) 字符设备注册函数. 它在 2.6 内核中被模拟, 但是不应当给新代码使用. 如果主编号不是 0,
可以不变地用它; 否则一个动态编号被分配给这个设备.
int unregister_chrdev(unsigned int major, const char *name);
恢复一个由 register_chrdev 所作的注册的函数. major 和 name 字符串必须包含之前用来注册设备时同样的值.
struct file_operations;
struct file;
struct inode;
大部分设备驱动使用的 3 个重要数据结构. file_operations 结构持有一个字符驱动的方法; struct file 代表
一个打开的文件, struct inode 代表磁盘上的一个文件.
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
cdev 结构管理的函数, 它代表内核中的字符设备.
#include <linux/kernel.h>
container_of(pointer, type, field);
一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结构的指针.
#include <asm/uaccess.h>
这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.
unsigned long copy_from_user (void *to, const void *from, unsigned long count);
unsigned long copy_to_user (void *to, const void *from, unsigned long count);
在用户空间和内核空间拷贝数据.
----------------------------------------------------------------------------------------------
4、驱动程序学习的要隘
答:<1>弄清楚现有驱动程序的框架,在这个框架中加入硬件。
<2>各种机制的学习:并发与竞态,异步通知机制,poll机制(定时查询机制),阻塞非阻塞等等。
<3>待续。
5、驱动程序到硬件之间的实现是内核的工作,了解其工作原理有利于理解整个操作系统,但是就驱动学习而言,
主要的学习内容是上面【4、】的内容,分清!分清!!
6、为什么第一个按键中断驱动程序的xx_read()函数中,加入对阻塞的判断:如不阻塞操作,返回-EAGAIN;
在本例中不管是阻塞操作还是不阻塞操作,其结果都是在中断中唤醒进程(或程序),且每读操作一次就在其后的第二次读操作时休眠进程,
这两种貌似没有什么区别。
7、进程定义的个人理解
答:按键课程中在终端可用 ps 命令查看所有在运行的进程 PID ,
进程,广义讲是由应用程序或命令的执行开始,到其调用驱动程序,再到得到所需要的执行结果返回应用程序并结束为止,的一系列系统执行流程步骤。
进程,狭义讲是系列系统执行流程步骤中的当前此刻CPU执行的程序语句命令或程序调用。
8、解决并发和竞态的机制
答:【由来】并发和竞态通常作为对资源的共享访问结果而产生。或者说同一个设备/驱动程序对不同的应用程序/调用者来说是唯一且可抢占的,
如此即造成不同的应用程序/调用者竞争同一个设备/驱动程序,结果是设备/驱动程序的运行必将是混乱的。
【目的】如何给 scull 加锁?我们的目标是使我们对 scull 数据结构的操作原子化, 这就意味着在涉及其他执行线程之前,整个操作就已经结束了。
【方法】:使用访问管理的技术,通常称为“锁定”“互斥”——确保一次只有一个执行线程可操作共享资源。
为此,需要建立临界区:在任意给定的时刻,代码只能被一个线程执行。
<1>原子变量:内核提供了一种原子的整数类型,称为 atomic_t ,定义在<asm/atomic.h>中。
使用情景:当共享资源可能恰好是一个整数值。
方法:即把有原子变量操作的代码段包含入驱动的xx_open()函数开头处,如此由于原子操作的天然执行过程中不会被别的代码/路径/线程
所中断的特性,即达到同一时刻,只能有一个应用程序 open 该驱动程序的目的。
效果:绝对排斥对模块的二次及更多次调用,使得主调者即应用程序不能open成功。
原子操作的速度非常快,只要可能,它们会被编译成单个机器指令,(并且不需要禁止中断?)。
说明:原子变量不能传递给需要整型参数的函数,否则会编译错误。原子变量的数目也是原子的。
<2>使用锁定机制:信号量和互斥体,
方法:当一个线程成功调用down的某个版本之后,就称为该线程“获得”了该信号量。这样,该线程就被赋予访问由该信号量保护的临界区的权利。
当互斥操作完成之后(一般是进程关闭时),必须用函数up(struct semaphore name)返回该信号量。
方法:同<1>原子变量 即把down的某个版本的代码段包含入驱动的xx_open()函数开头处,up(name)放到 xx_close 的结尾处。
说明:正确使用信号量锁定机制的关键是,明确指定需要保护的资源,并确保每一个对这些资源的访问使用正确的锁定。例如,LDD3第五章_并发与
竞态,在驱动程序中包含所有信息的 scull_dev 结构,即是我们锁定机构的逻辑范围。或者使用单个全局的信号量也是正确的。
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore *///【在这里!!】
struct cdev cdev; /* Char device structure */
};
效果:对模块二次调用产生的进程将是僵死状态,其在一次调用的进程释放信号量之后才能恢复到正常状态并执行。
<3>
【优化】不同的 scull 设备并不共享资源,因此没有理由让一个进程在其他进程访问不同的 scull 设备时等待。为每个设备使用单独的信号量
允许不同的设备上的操作可以并行处理,从而可以提高性能。
总结:解决并发和竞态的的方法就是给需要保护的不能被别的线程修改甚至访问的资源上锁,常用的锁机制简单如原子变量,完整的如信号量。
9、.release被调用的时机?
答:内核在进程退出的时候,通过内部使用close系统调用自动关闭所有相关的文件。即执行系统调用open(或者别的指令)所创建的打开文件的副本,
都会在程序终止时系统执行一次release系统调用后被关闭。
10、static 修饰的静态全局变量的在模块执行、卸载、再执行、系统重启过程中的变化?
百度百科:模块的代码开始运行后,使用 Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用 Static
语句显式声明只在该过程内可见,但具有与包含该过程定义的模块相同生命期的变量。可以在过程中使用 Static 语句来声明在过程调用之间仍能保持
其值的变量的数据类型。
问题:在调用驱动程序的进程被杀死后,驱动程序不卸载或者说应用程序紧接着再次调用驱动,驱动程序原来运行的数据还会保存着吗?
数据:变量;
答:
11、内核函数返回值的情况
答:LDD3: 大多数内核函数会在成功时返回0。
12、多线程操作同一个函数,驱动程序,结果? 事务与多线程和同步之间的关系?
答:事务为保证一个操作的原子性而设置的,一个事务必定包含多个操作,多个操作再逻辑上要保证完整一致,如果中间只要有一个操作失败,那么事务必
须回滚,必须回到整个操作的初始状态。
多线程为了提高应用的执行效率而设置的,多个线程可以做同样的事情或不同的事情,单个线程只能处理1个客户请求,那么多线程就可以同时处理多
个请求。每一个线程处理的业务涉及到多个操作,如果有一致性的要求,那么必须介入事务。
同步是为了解决多线程使用过程中,使用相同资源导致数据不一致而引入的,使用了同步机制,那么多个线程在访问同一资源时,必须等到另一个线
程使用完毕,释放了这个资源,其它的线程才有机会使用。
13、休眠状态是如何保存进入并退出的?即其在内核中的机制原理?
14、测试:
1、在驱动中用 static 定义一个全局变量和原子变量,执行应用程序调用驱动,查看:
<0>一次调用时,变量的值;
<1>二次调用时,变量的值;
<2>杀死一次调用,不卸载驱动,变量的值的变化;
目的:查看并发和竞态环境对驱动模块的作用情况;
源码程序:
File: keys_drv_4.c
static int num = 0;
static int keys_drv_open(struct inode *inode, struct file *filp)
{ int i; int err = 0; num += 2; //源码其他处,num只有打印不做操作;
printk("keys_drv_open: 1_ num = %d\n", num); //测试1;
down(&button_lock);
printk("keys_drv_open: 2_ num = %d\n", num); //测试2;
request_irq(...);
...
}
static int keys_drv_close(struct inode *inode, struct file *filp)
{ int i;
/* 释放按键中断源 */
free_irq(pinsdesc[i].irq, (void *)&pinsdesc[i]);
...
printk("keys_drv_close: 1_ num = %d\n", num);//测试1;
up(&button_lock);
printk("keys_drv_close: 2_ num = %d\n", num);//测试2;
return 0;
}
远程服务器终端打印如下:
1.----------------------------------------------
# insmod keys_drv_4.ko
# ./keys_drvtest_4 &
# keys_drv_open: 1_ num = 2
keys_drv_open: 2_ num = 2
# ps
PID Uid VSZ Stat Command
...
767 0 3096 S -sh
811 0 1312 S ./keys_drvtest_4
812 0 3096 R ps
# Time out!
# kill -9 811
# keys_drv_close: 1_ num = 2
keys_drv_close: 2_ num = 2
2.-----------------------------------------------
# ./keys_drvtest_4 &
# keys_drv_open: 1_ num = 4
keys_drv_open: 2_ num = 4
# ps
PID Uid VSZ Stat Command
...
767 0 3096 S -sh
813 0 1312 S ./keys_drvtest_4
3.-----------------------------------------------
# ./keys_drvtest_4 &
# keys_drv_open: 1_ num = 6
# ps
Time out!
PID Uid VSZ Stat Command
...
767 0 3096 S -sh
813 0 1312 S ./keys_drvtest_4
815 0 1308 D ./keys_drvtest_4
816 0 3096 R ps
4.-----------------------------------------------
# ./keys_drvtest_4 &
# keys_drv_open: 1_ num = 8
# ps
PID Uid VSZ Stat Command
...
767 0 3096 S -sh
813 0 1312 S ./keys_drvtest_4
815 0 1308 D ./keys_drvtest_4
817 0 1308 D ./keys_drvtest_4
818 0 3096 R ps
# Time out!
5.-----------------------------------------------
# kill -9 813
# keys_drv_close: 1_ num = 8
keys_drv_close: 2_ num = 8
keys_drv_open: 2_ num = 8
# Time out!
# kill -9 815
# keys_drv_close: 1_ num = 8
keys_drv_close: 2_ num = 8
keys_drv_open: 2_ num = 8
# Time out!
# kill -9 817
keys_drv_close: 1_ num = 8
keys_drv_close: 2_ num = 8
# ps
PID Uid VSZ Stat Command
...
767 0 3096 S -sh
820 0 3096 R ps
6.-----------------------------------------------
# rmmod keys_drv_4
# insmod keys_drv_4.ko
# ./keys_drvtest_4 &
# keys_drv_open: 1_ num = 2
keys_drv_open: 2_ num = 2
Time out!
<结束>
结论:
对比1.与2.发现:两个甚至多个应用程序调用驱动,则驱动都响应且响应结果累加,即说明某设备的驱动一旦加载则其在内存中的存储位置将不变且唯一!
这就是并发和竞态造成的结果!
对比1-5.与6.发现:当驱动卸载之后,自该驱动加载之后所有生成的数据都被清除!