【驱动】第1课、字符设备驱动之学习笔记

=====节一、字符设备驱动程序之概念介绍=====
1、模块(即某单一驱动)是如何构建的?
答:构建一个最基本的驱动模块,只需要4函数+1头文件:模块装载函数xx_init(), 模块卸载函数xx_exit(), module_init(), module_exit(), <linux/init.h, module.h>;

后来复习更应该归纳为这样5项:<1>major;<2>file_operations{...}  <3>register_chrdev(major, name, &fops);  <4>入口函数xx_init();<5>出口函数xx_exit()。
如此,用Makefile编译,即可生成一个xx.ko这个最基本的且可以编译成功并在单板平台加载/卸载/运行的驱动模块,在单板串口控制台(即终端)即可执行加载/卸载任务。

2、模块(即某单一驱动)为何可以如此构建使用?即linux内核给模块(即驱动)的构建,挂接,卸载提供了什么支持?

答:<1>驱动框架的构建;<2>库函数;<3>

3、应用程序是怎么调用驱动的?模块(即某单一驱动)是如何工作的?需要什么样的环境?比如:需要什么样的编译器,Makefile,C库函数,?
答:应用最终是读/写内存,通过 open, read, write 等标准接口调用驱动程序的;
open, read, write 等函数是在C库中实现的;
open, read, write 等标准接口通过 swi_irq指令,即软中断进入异常管理模式,进入特权模式,处理异常的程序操作(只有超级用户才有权加载和卸载模块);
驱动的本质就是操作寄存器的代码;

4、字符设备驱动程序如何分析?
答:沿着数据流向,情景分析;

5、操作系统和CPU的关系?编译工具链gcc和arm-linux-gcc的区别和编译出的文件的区别?
问题来源:编译出来的xx.ko驱动模块,可以在单板平台执行,却不能在Linux服务器的远程服务器终端执行,是因为服务器和单板上的操作系统不是一个版本吗?
或者说同一程序由不同的编译工具链编译出来的二进制代码是不同的,只能在指定平台执行?
答:

=====节二~七、LED驱动,按键驱动=====
1、驱动加载并使用的三个基本函数/命令的作用?
register_chrdev(0, "led_1", &led_fops); //驱动模块中; 注:"led_1"应改为:"leds",同设备的类的名字;
# mknod /dev/huhu c 252 0; //命令;
open("/dev/huhu", O_RDWR); //测试程序中;
答:在加载驱动模块时,函数 register_chrdev(0, "led_1", &led_fops); 是以主设备号、次设备号及其file_operations结构体来注册设备的,
说是设备名字“led_1”无用,但是在测试程序中open("/dev/huhu", O_RDWR);打开设备又是依据什么参数呢?看来是字符串"/dev/xxx",这是使用命令:
# mknod /dev/huhu c 252 0
创建了一个特殊文件huhu,并且这个特殊文件即设备文件的主设备号252,次设备号0,指定路径为/dev/huhu,用于作为文件系统的节点。
由此可知,函数open("/dev/xxx", O_RDWR)内,节点文件的名字只是作为路径节点,供连接查找之用,别无它用,而非单纯的说"/dev/huhu"名字不重要!
(注:"/dev/huhu"的名字"huhu"是在mknod命令或者class_device_create()函数内设置的)。
从上可以看出,open()打开的是已知主设备号、次设备号的准确的设备,而register()函数注册的设备的驱动模块只有主设备号,在该类设备下只有一个设备是
没有问题?但是,当该设备类下有多个该类设备的话,该怎么办?是因为该类设备都调用同一个设备模块即 file_operations 结构体吗?代码段:
led_class = class_create(THIS_MODULE, "leds");
led_class_dev = class_device_create(led_class, NULL, MKDEV(major, 0), NULL, "huhu2");
上面两个函数,主要功能是为了使得mdev机制在/dev目录下自动创建主/次设备号为(major, 0)的设备的系统节点文件"/dev/huhu"提供必要
的系统信息(包含major, minor, device_name等)。
register_chrdev(0, "leds", &led_fops);用于注册设备类‘’leds‘’的驱动程序到内核的字符设备数组chrdev;
mknod /dev/huhu c 252 0;用于创建可以连接到设备x的模块的对外接口结构体的节点文件:
open("/dev/huhu", O_RDWR);用于打开的某个设备,需根据其节点文件"/dev/xyz",即调用其对应的驱动模块。
问题:xxx_init();函数只能加载一个设备还是一类设备到内核?open()函数是调用一个设备还是一类设备?

答:xxx_init()入口函数加载的设备数目取决于register_chrdev()函数,与入口函数本身并无关系。open()函数打开的是leds类下的某设备/dev/huhu。
(4)关于名字的问题,驱动模块的名字"led_1"会在目录/proc/devices中显现; 设备的类别名字会在目录/sys/class中显现;
具体设备的节点文件名字会在目录/sys/class/设备类别 , /dev中显现。
为使正规, 规定如下:驱动模块的名字:"xxx_drv"; 设备的类别名字: "xxxs"; 具体设备的节点文件名字同设备名字:"xxx"。可否?
在myleds文件系统中的用法如下:
#define DEVICE_NAME "leds" /* 加载模式后,执行”cat /proc/devices”命令看到的设备名称 */
ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &s3c24xx_leds_fops);
leds_class_devs[0] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, 0), NULL, "leds"); /* /dev/leds */
leds_class_devs[minor] = class_device_create(leds_class, NULL, MKDEV(LED_MAJOR, minor), NULL, "led%d", minor);

2、LED设备驱动增加
1)驱动源代码文件中:
a. 定义指向寄存器的虚拟地址的空指针;
b. 在xx_init()函数中映射使用的寄存器的物理地址成虚拟地址;
c. 在open, read, write操作中,操作寄存器;
d.
2)驱动测试程序文件:
函数原型:int main(int argc, char ** argv){ 。。。}
a. open设备,并根据返回值fd判断设备打开是否成功;
b.
注:第一个驱动模块的学习,重点在框架搭建和各种函数结构体的学习使用!大部分函数结构体的使用查百度百科,百度文库,华清远见的文章!

3、函数xx_write()内为什么要用函数copy_from_user()到本地再使用?使用的参数既然已经通过形参传入,为什么不直接使用?
static ssize_t s3c2440_leds_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{ ...
copy_from_user(&val, buf, count);
...
}
答:是因为怕改动原参数吗?可屏蔽该句代码实验一下吗?

答2:根据LDD3>>字符设备驱动程序>>read 和 write 章节说明: read 和 write 方法的 buff 参数是用户空间的指针。因此,内核代码不能直接引用其中的内容。至于造成这种限制的原因,如下:

● 依赖于你的驱动运行的体系, 以及内核被如何配置的, 用户空间指针当运行于内核模式可能根本是无效的. 可能没有那个地址的映射, 或者它可能指向一些其他的随机数据.

● 就算这个指针在内核空间是同样的东西, 用户空间内存是分页的, 在做系统调用时这个内存可能没有在 RAM 中. 试图直接引用用户空间内存可能产生一个页面错, 这是内核代码不允许做的事情. 结果可能是一个"oops", 导致进行系统调用的进程死亡.

● 置疑中的指针由一个用户程序提供, 它可能是错误的或者恶意的. 如果你的驱动盲目地解引用一个用户提供的指针, 它提供了一个打开的门路使用户空间程序存取或覆盖系统任何地方的内存. 如果你不想负责你的用户的系统的安全危险, 你就不能直接解引用用户空间指针.

 

4、驱动程序加载成功到内核之后,对于设备文件的属性的查看
答:步骤如下:
# insmod led_1.ko
# cat /proc/devices
Character devices:
1 mem
...
252 leds
...
# ls -l /dev/
crw-rw---- 1 0 0 5, 1 Jan 1 02:28 console
...
crw-rw---- 1 0 0 252, 1 Jan 1 02:26 led1
crw-rw---- 1 0 0 252, 2 Jan 1 02:26 led2
crw-rw---- 1 0 0 252, 3 Jan 1 02:26 led3
crw-rw---- 1 0 0 252, 4 Jan 1 02:26 led4
crw-rw---- 1 0 0 252, 0 Jan 1 02:26 leds
...

其中,文件属性依次为:【文件权限】【连接】【所有者】【用户组】【主设备号】【次设备号】【最近的修改时间】【设备文件名字】。

注:【连接】表示有多少文件名连接到此节点(i-node)。


=====节八、中断方式查询按键发生=====
1、在中断申请函数request_irq()中: request_irq(IRQ_EINT19, keys_irq, IRQT_BOTHEDGE, "key_5", &pins_desc[3]);
中断处理属性项 irq_flags 设为 IRQT_BOTHEDGE , 因此,按键做外部中断源的中断触发方式是双边沿触发。
上机验证:进行一次按键“按下”“松开”操作,串口终端控制台会打印字符串:key_val = 0x81 两次,但有时也会打印3次,原因?
答:按键抖动吗?

2、wake_up_interruptible(keys_waitq);的操作原理是什么?为什么操作一个看似和程序无关的变量keys_waitq(?)即可唤醒这个中断处理进程?
休眠进程,休眠的是谁?中断?驱动?驱动的应用程序?
答:唤醒队列,触发进程;
static ssize_t keys_drv_read(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{
...
/* 休眠进程 */
wait_event_interruptible(keys_waitq, ev_press);
/* 上传中断引脚的值 */
copy_to_user(buf, &key_val, 1);
/* 标记进程的休眠标记 */
ev_press = 0;
...
}
上面的代码中,休眠和上传的额函数的前后顺序?
当copy_to_user(buf, &key_val, 1);在wait_event_interruptible(keys_waitq, ev_press);之后执行,打印异常:
key_val = 0x40
key_val = 0x10
key_val = 0x11
key_val = 0x10
key_val = 0x11
key_val = 0x10

key_val = 0x11
key_val = 0x40
当如上程序所示,copy_to_user(buf, &key_val, 1);在wait_event_interruptible(keys_waitq, ev_press);之前执行,打印正常:
key_val = 0x10
key_val = 0x11
key_val = 0x10
key_val = 0x11
原因何在?
答:static unsigned char key_val;变量定义时没有用volatile修饰吗?

3、虽然已经可以默写中断方式的按键驱动,但是,对程序内的函数和各种机制(例如:中断)的详细工作原理还是一知半解,徒之奈何!


=====节九、poll机制_指定时间查询按键发生=====
1、poll和定时器的作用原理有些像,但是区别是定时器在设定时间到之后必定进入定时器中断,而poll机制则在poll()函数发生error/成功/超时时,
跳出poll函数并返回一个返回值继续执行其后的程序。

2、是否就是说,有了poll机制的poll_wait(file, &button_waitq, wait)后,可以省略驱动程序xx_read()中的【函数1】了吗?
【函数1】wait_event_interruptible(button_waitq, ev_press);
例程:ssize_t forth_drv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{ ...
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
...
}
答:是的,经验证,省略驱动程序xx_read()中的函数:wait_event_interruptible(button_waitq, ev_press) 之后,在串口的打印结果一样:
Time out!
key_val = 0x10
key_val = 0x11
key_val = 0x40
key_val = 0x40

3、问题:系统调用open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll。
但他们和定义的file_operations结构体的.read, .write, .open, .poll元素(函数)有什么关系?
答:以poll为例,应用程序调用函数poll(),则会调用内核中的sys_poll函数,进而调用:
poll > sys_poll > do_sys_poll > poll_initwait 和 do_poll;(接下)
(接上):poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); 其中,table->qproc=__pollwait;
(接上):do_poll > for(;;) > do_pollfd > mask=file->f_op->poll(file, pwait) -> __pollwait;
各函数功能说明:
3.1、内核框架:
对于系统调用poll或select,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。
(1) sys_poll函数位于fs/select.c文件中,代码如下:...它对超时参数稍作处理后,直接调用do_sys_poll。
(2) do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他代码:
(3) poll_initwait函数非常简单,它初始化一个poll_wqueues变量table:
poll_initwait > init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;
即table->pt->qproc = __pollwait,__pollwait将在驱动的poll函数里用到。
(4) do_sys_poll函数位于fs/select.c文件中,代码如下:
分析其中的代码,可以发现,它的作用如下:
①从02行可以知道,这是个循环,它退出的条件为:
a.09行的3个条件之一(count非0,超时、有信号等待处理); count非0表示04行的do_pollfd至少有一个成功。
b.11、12行:发生错误
②重点在do_pollfd函数,后面再分析
③第30行,让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。
那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用
poll_wait的原因,后面分析。
(5) do_pollfd函数位于fs/select.c文件中,代码如下:可见,它就是调用我们的驱动程序里注册的poll函数。
3.2、驱动程序:
驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到
的__pollwait函数。
(1) pollwait的代码如下:p->qproc就是__pollwait函数,从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列里而已。它的代码如下:
执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的
do_sys_poll函数的30行“__timeout = schedule_timeout(__timeout)”。
poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll > do_sys_poll > poll_initwait,do_poll > do_pollfd > 我们自己
写的poll函数后,再调用schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait
的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:chedule_timeout(__timeout),只是要
休眠__time_out这段时间。
(2) file->f_op->poll即forth_drv_fops->poll,在本节课程程序中即:forth_drv_poll(){... poll_wait(file, &button_waitq, wait) ...}。
综述,现在来总结一下poll机制:
1. poll > sys_poll > do_sys_poll > poll_initwait,poll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait
时,真正被调用的函数。
2. 接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数
它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;
它还判断一下设备是否就绪。
3. 如果设备未就绪,do_sys_poll里会让进程休眠一定时间
4. 进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,
这个队列,就是前面通过poll_wait把本进程挂过去的队列。
5. 如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复2、3动作,直到应用程序的poll调用传入的时间到达。


4、问题:poll机制和定时器中断机制有什么区别?
答:a.两个机制都是等待一段时间再工作,但是poll是软件实现的机制,定时器中断是硬件实现的机制;
b. 定时器时间是硬性设置,除非发生更高级的异常,否则就会等到定时结束执行定时器中断函数,而poll则是有条件把该进程在此处【注1】休眠,
唤醒的条件:a.09行的3个条件之一(count非0,超时、有信号等待处理),count非0表示04行的do_pollfd至少有一个成功。//count=poll()函数返回值。
b.11、12行:发生错误
可以查看上面的摘抄的笔记。
当条件成立时则会退出休眠到应用程序poll()函数之后继续执行后面的程序。
或者通过驱动程序硬性唤醒休眠,如同第third_drv.c中做的一样: wake_up_interruptible(&button_waitq);
c. 休眠中途可以因为条件触发而唤醒进程,且再次休眠时当重新计时,而中断则雷打不动的严格按照设定好的计时一次一次周而复始的进行定时器中断处理。
d. 定时器中断属于异常,poll属于一种把当前的文件指针挂到等待队列的阻塞机制,属于软件功能。
注1:“此处”指wait_event_interruptible(button_waitq, ev_press); 或 do_sys_poll函数的30行“__timeout = schedule_timeout(__timeout)”。

5、问题:poll机制和定时器中断机制有什么相同处?
答:都是在指定的时间内查询是否有预想的事件(例如按键按下)发生;

6、驱动程序与poll的关系?
答:驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到
的__pollwait函数。

7、main(int argc, char **argv)
{ fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
printf("can't open!\n");
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{ ret = poll(fds, 1, 5000);
if (ret == 0)
printf("time out\n");
else
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
} } }

问什么open返回值 fd 可以做参数?fd作用? 其中原理何在?
答:1>fd为文件描述符,内核(kernel)利用文件描述符(file descriptor)来访问文件。文件描述符是非负整数。打开现存文件或新建文件时,
内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。是一个非负整数。
文件描述符是由无符号整数表示的句柄,进程使用它来标识打开的文件。文件描述符与包括相关信息(如文件的打开模式、文件的位置类型、
文件的初始类型等)的文件对象相关联,这些信息被称作文件的上下文。
对于每个进程,操作系统内核在u_block结构中维护文件描述符表,所有的文件描述符都在该表中建立索引。

2>与文件描述符相关的操作:
文件描述符的生成: open(), open64(), creat(), creat64()...
与单一文件描述符相关的操作:read(), write(), recv(), send()...
与复数文件描述符相关的操作:select(), pselect(),poll()...
与文件描述符表相关的操作: close(),dup()...
改变进程状态的操作: fchdir(),mmap()...
与文件加锁的操作: flock(),fcntl (F_GETLK, F_SETLK and F_SETLKW),lockf()...
与套接字相关的操作: connect(),bind(),listen(),accept()...

8、需要补习的课程?
答:1>字符驱动的深入重新学习; 2>进程的休眠与唤醒; 3>poll机制及其与休眠唤醒的关系; 4>异常与中断处理;


=====节十、异步通知_指定时间查询按键发生=====
1、课堂笔记
目的:使用异步通知机制,当设备资源可获得,例如发生按键中断时,调用kill_fasync()函数激发相应的信号xx(例如:SIGIO),
触发系统调用函数signal(),最终执行信号注册函数 signal() 中该信号xx对应的信号处理函数。
整个异步通知机制fasync工作流程:
a. 应用程序注册信号处理函数: signal(SIGIO, my_signal_fun);
b. 谁发?驱动发信号给应用程序的系统调用函数: signal(SIGIO, my_signal_fun);
c. 发给谁?发给应用程序;
d. 怎么发?

驱动块:
1)结构体 butoon_async 在某字符驱动结构体的元素 xx_fops.fasync 对应的函数 fifth_drv_fasync 中被函数
fasync_helper(fd,filp,on,&button_async) 初始化,如此才可使用该结构体 butoon_async,而调用该结构体的
函数 kill_fasync() 才能使用。
说明:fasync_helper()是内核做的一个辅助函数,其作用就是初始化结构体 butoon_async。
2)本例程中,按键按下,触发中断处理程序 buttons_irq(),其内kill_fasync()函数将发送信号 SIGIO 给结构体
butoon_async 中包含的进程号PID为xx的进程,该信号将触发应用程序中的信号处理函数signal(),并根据接收到
的信号 SIGIO 执行其对应的信号处理函数 my_signal_fun() 函数。

应用块:
1)注册信号处理函数 signal(SIGIO, my_signal_fun),给信号 SIGIO 挂接一个处理函数 my_signal_fun(), 当外部
发送信号 SIGIO 给系统调用函数 signal() 时,就会执行函数 my_signal_fun。
2)执行以下三步:
fcntl(fd, F_SETOWN, getpid()); // 应用程序通过该指令告诉驱动程序,信号 SIGIO 发给哪个进程。
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct结构体。
将初始化驱动程序中的 fasync_struct 结构体,并在该结构体中标注接收该驱动程序发送的信号的进程的 PID,即执行该应用程序产生的PID。

综述,有按键按下,触发按键外部中断,执行中断处理函数:buttons_irq(),其内执行指令:kill_fasync(&button_async, SIGIO, POLL_IN),
如此,该驱动(即: fifth_drv.ko)将向结构体 button_async 中包含的进程号 PID 代表的进程(即:fifth_drvtest)发送信号 SIGIO,
该进程收到信号 SIGIO,将触发signal()系统调用,并执行 SIGIO 对应的信号处理函数 my_signal_fun()。


=====节十一、同步,互斥,阻塞=====
目的:同一时刻,只能有一个应用程序打开驱动程序: /dev/buttons。

1.用信号量的方法定义一个互斥锁,实现同一时刻,只能有一个应用程序打开驱动程序: /dev/buttons 的目的。
问题:为什么执行命令:kill -9 846,杀掉PID为846的进程 ./sixthdrvtest后,正处在休眠等待获得信号量的
另一个进程(例如:第二次执行应用程序./sixthdrvtest产生的另一个进程)就会立马获得信号量并且运行?
期间发生了什么?信号量不是只有执行命令:up(&button_lock) 才能释放信号量吗?是否进程被kill之后,
其所有的数据都被清除?
答:信号量本质是一个整数值,和函数 down(信号量sem) 和 up(信号量sem) 联合使用。希望进入临界区的进程将在相关信号量上调用 down(信号量sem)函数,只有信号量的值大于0,进程才可以继续;相反,若信号量等于甚至小于0,进程将必须在此(down(信号量sem)函数处)等待直到其他人释放该信号量。

进程被杀掉的命令会自动执行模块的 .release 成员参数对应的 xx_close 函数,即间接执行 up() 函数。 

2、用非阻塞操作和信号量的方法,进行试验时,发现不同于视频的试验数据:
有明显的按键值打印延迟,且5秒之后没有自动打印!原因?

 

=====节十二、定时器防抖=====

1、定时器按键防抖动
问题1:定时器 xx_timer 触发的条件是计数系统中断次数的全局变量 jiffies 相等于还是大于等于定时器的超时时间 xx_timer.expires ?
定时器 xx_timer 是为了给按键中断消抖用的,若是以上两种触发的任意一种,该怎样防止多余的定时器中断?
答:未查到具体使用说明。但根据视频的打印结果显示:执行语句add_timer()之后,程序会立马执行该定时器的中断处理函数。
另,按键之后“按下”“松开”两个打印,不按键时没有打印。
问题2:根据下面的资料,mod_timer(&buttons_timer, jiffies+HZ/100)中的 jiffies+HZ/100 即 xx_timer.expires ,但是执行add_timer()
产生的第一次定时器中断的原因是什么?
【资料:】
1、内核定时器的调度函数运行过一次后就不会再被运行了(相当于自动注销),但可以通过在被调度的函数中重新调度自己来周期运行。
在SMP系统中,调度函数总是在注册它的同一CPU上运行,以尽可能获得缓存的局域性。
2、int timer_pending(const struct timer_list *timer) 这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。
注意,当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)。
【例程:】
File: buttons.c
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
/* 10ms后启动定时器 */
irq_pd = (struct pin_desc *)dev_id;
mod_timer(&buttons_timer, jiffies+HZ/100);
printk("buttons_irq--0: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
return IRQ_RETVAL(IRQ_HANDLED);
}
...
static void buttons_timer_function(unsigned long data)
{
struct pin_desc * pindesc = irq_pd;
unsigned int pinval;
printk("buttons_timer_function--1: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
if (!pindesc) // 无此语句,程序将崩溃!
return;
printk("buttons_timer_function--2: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
pinval = s3c2410_gpio_getpin(pindesc->pin);
...
}
...
static int sixth_drv_init(void)
{
init_timer(&buttons_timer);
buttons_timer.function = buttons_timer_function;
//buttons_timer.expires = 0;
add_timer(&buttons_timer);
printk("sixth_drv_init: jiffies = %ld, buttons_timer.expires = %ld\n", jiffies, buttons_timer.expires);
...
}
File: buttons_test.c
int main(int argc, char **argv)
{...
fd = open("/dev/buttons", O_RDWR); //若改为: O_RDWR|O_NONBLOCK;则程序崩溃,不停的打印:key_val: 0x0, ret = -1
}
【打印结果:】
# insmod buttons.ko
sixth_drv_init: jiffies = 329832, buttons_timer.expires = 0
buttons_timer_function--1: jiffies = 329833, buttons_timer.expires = 0
#
# ./buttons_test &
#
# buttons_irq--0: jiffies = 336155, buttons_timer.expires = 336157
buttons_timer_function--1: jiffies = 336157, buttons_timer.expires = 336157
buttons_timer_function--2: jiffies = 336157, buttons_timer.expires = 336157
key_val: 0x1, ret = 1
buttons_irq--0: jiffies = 336185, buttons_timer.expires = 336187
buttons_timer_function--1: jiffies = 336187, buttons_timer.expires = 336187
buttons_timer_function--2: jiffies = 336187, buttons_timer.expires = 336187
key_val: 0x81, ret = 1
<结束>
问题3:加载模块和执行应用程序时,使用下面的代码将造成程序崩溃!
(1)if (!pindesc) // 无此语句,程序将崩溃!
return;
(2)fd = open("/dev/buttons", O_RDWR); //若改为: O_RDWR|O_NONBLOCK;则程序崩溃,不停的打印:key_val: 0x0, ret = -1
原因何在?
答:不知。

 

2、内核定时器使用规则

当一个定时器运行时, 但是, 这个调度进程可能睡
眠, 可能在不同的一个处理器上运行, 或者很可能已经一起退出.
这个异步执行类似当发生一个硬件中断时所发生的( 这在第 10 章详细讨论 ).
实际上, 内核定时器被作为一个"软件中断"的结果而实现. 当在这种原子上下文
运行时, 你的代码易受到多个限制. 定时器函数必须是原子的以所有的我们在第
1 章"自旋锁和原子上下文"一节中曾讨论过的方式, 但是有几个附加的问题由于
缺少一个进程上下文而引起的. 我们将介绍这些限制; 在后续章节的几个地方将
再次看到它们. 循环被调用因为原子上下文的规则必须认真遵守, 否则系统会发
现自己陷入大麻烦中.
为能够被执行, 多个动作需要进程上下文. 当你在进程上下文之外(即, 在中断
上下文), 你必须遵守下列规则:

  • 没有允许存取用户空间. 因为没有进程上下文, 没有和任何特定进程相关联的到用户空间的途径.
  • 这个 current 指针在原子态没有意义, 并且不能使用因为相关的代码没有和已被中断的进程的联系.
  • 不能进行睡眠或者调度. 原子代码不能调用 schedule 或者某种wait_event, 也不能调用任何其他可能睡眠的函数. 例如, 调用kmalloc(..., GFP_KERNEL) 是违犯规则的. 旗标也必须不能使用因为它们可能睡眠.

不应当被忘记的定时器的一个重要特性是, 它们是一个潜在的竞争条件的源, 即便在一个单处理器系统. 这是它们与其他代码异步运行的一个直接结果. 因此,任何被定时器函数存取的数据结构应当保护避免并发存取, 要么通过原子类型( 在第 1 章的"原子变量"一节) 要么使用自旋锁( 在第 9 章讨论 ).

 

posted @ 2018-11-26 18:15  大秦长剑  阅读(413)  评论(0编辑  收藏  举报