七、 字符设备驱动程序之poll机制(第九课)
对于系统的调用,基本都可以在它的名字前加上 "sys_" 前缀,这就是内核中对应的函数。比如系统调用 open、read、write、poll,与之对应的内核函数为:sys_open、sys_read、sys_write、sys_poll
sys_poll 函数位于 fs/select.c 文件中
poll_initwait 函数的作用是初始化一个 poll_wqueues* 类型的变量 table
__pollwait 这个函数把我们当前进程挂入我们驱动程序里定义的一个队列里
接着分析 (do_poll) 函数
break的条件:1、count非零(驱动程序中的 ev_press 变量为1时,也就是中断发生,驱动程序的函数返回非0值);2、超时;3、有信号等待处理
否则进入休眠
poll机制驱动程序(forth_chrdev.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
/*
* eint 0 GPF0
* eint 2 GPF2
* eint 11 GPG3
* eint 19 GPG11
*/
static struct class *forth_chrdev_class;
static struct class_device *forth_chrdev_class_dev;
static volatile unsigned char key_val;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned char pinval;
printk("irq = %d
", irq);
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval)
{
/* 松开 */
key_val = pindesc->key_val;
}
else
{
/* 按下 */
key_val = 0x80|pindesc->key_val;
}
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
ev_press = 1;
return IRQ_HANDLED;
}
static int forth_chrdev_open(struct inode *inode, struct file *file)
{
printk("forth_chrdev_open
");
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
static ssize_t forth_chrdev_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
if (count != 1)
return -EINVAL;
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return 1;
}
int forth_chrdev_release(struct inode *inode, struct file *file)
{
printk("forth_chrdev_release
");
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
static unsigned int forth_chrdev_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
//不会立即休眠,只是把当前进程挂接进去,也就是执行指针指向后的__pollwait函数。
poll_wait(file, &button_waitq, wait);
if (ev_press)
mask |= (POLLIN | POLLRDNORM);
return mask;
}
static struct file_operations forth_chrdev_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = forth_chrdev_open,
.read = forth_chrdev_read,
.release = forth_chrdev_release,
.poll = forth_chrdev_poll, //这个.poll所指向的函数最终是在(do_pollfd)函数中的(file->f_op->pll)函数中被调用
};
int major;
static int forth_chrdev_init(void)
{
major = register_chrdev(0, "forth_chrdev", &forth_chrdev_fops); //注册
forth_chrdev_class = class_create(THIS_MODULE, "forthchrdev");
forth_chrdev_class_dev = class_device_create(forth_chrdev_class, NULL, MKDEV(major, 0), NULL, "forth_chrdev");
return 0;
}
static void forth_chrdev_exit(void)
{
unregister_chrdev(major, "forth_chrdev"); //卸载
class_device_unregister(forth_chrdev_class_dev);
class_destroy(forth_chrdev_class);
}
module_init(forth_chrdev_init);
module_exit(forth_chrdev_exit);
MODULE_LICENSE("GPL");
poll机制测试程序(forth_chrdev_test.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
int main(int argc, char **argv)
{
int fd,ret;
volatile unsigned char key_val;
struct pollfd fds[1];
fd = open("/dev/forth_chrdev",O_RDWR);
if(fd < 0)
printf("can't open!
");
fds[0].fd = fd;
fds[0].events = POLLIN; //POLLIN There is data to read.
while(1)
{
//poll可以多个文件查询,此处只查询一个文件
ret = poll(fds, 1, 5000); //poll的返回条件有2:超时时间到;中断发生
if(ret == 0) //超时时间到则返回0
{
printf("timeout
");
}
else
{
read(fd,&key_val,1);
printf("key_val = 0x%x
", key_val);
printf("
");
}
}
return 0;
}
测试:
- 若五秒没有中断发生,则打印(timeout)字符串。
- 若任意时刻中断发生,则立刻响应中断
总结poll机制:
- poll>sys_poll>do_sys_poll>poll_initwait,poll_initwait 函数注册了函数 __pollwait,它就是我们驱动程序执行poll_wait时,真正被调用的函数。
- 接下来执行 file->f_op->poll,也就是我们驱动程序里自己实现的poll函数,它会调用 poll_wait函数,把自己挂入某个队列里,这个队列(button_waitq)是我们的驱动自己定义的,在挂入之前还会判断一下设备是否就绪。
- 进程被唤醒的条件有:一、超时时间到,二、被驱动程序唤醒。
- 如果进程被唤醒则返回,返回值为0表示超时时间到,返回非0则表示被驱动程序唤醒。
八、 字符设备驱动程序之异步通知(第十课)
获取按键值有三种方式:1.查询(耗资源);2.中断(虽然会休眠,但是如果一直没有按键按下的话会一直等待,永远不会返回);3.poll机制(指定超时时间返回)
这三种方法有共同点:都是应用程序主动的读
那么有没有一种方法,当有按键按下了会有驱动程序来提醒应用程序来读呢?
答:使用异步通知(用signal来实现),进程之间通过命令来发信号
kill -5 pid:向pid发送5这个信号
sigal.c
测试:
- 后台运行程序并查看pid
- 向应用程序的pid进程发送信号值(USR1=10)
向应用程序的pid进程发送这个信号值就可以执行我们自己实现的函数
信号要点:
- 应用程序注册信号处理函数
- 谁发?(驱动函数发)
- 发给谁?(发给应用程序,所以应用程序需要告诉驱动程序pid)
- 怎么发?(驱动程序的中断处理函数里会调用 kill_fasync 函数)
目标:按下按键时,驱动程序通知应用程序
首先搜索一下(kill_fasync),看看别人是怎么用的
查看一下这个结构是怎么定义的
这个结构被定义出来了,但是在什么地方被初始化的呢?由于这个结构被定义的 static 关键字,所以只在本文件能被使用,在本文件中搜索一下这个(rtc_async_queue)结构
同样这个(rtc_fasync)函数也被限制该文件内使用,搜索一下这个函数在什么地方被调用
1. 应用程序调用F_SETOWN命令之后就会把应用程序的pid告诉驱动程序,这个命令的处理是由内核帮我们做的。
2. F_SETFL命令是指每当flag被改变后,驱动程序中的fasync函数就会被调用。
- 应用程序会调用这个函数来告诉驱动程序我自己的pid
- 应用程序去读出flag
- 修改flag,加上fasync
异步通知程序(fifth_chrdev.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
/*
* eint 0 GPF0
* eint 2 GPF2
* eint 11 GPG3
* eint 19 GPG11
*/
static struct class *fifth_chrdev_class;
static struct class_device *fifth_chrdev_class_dev;
static volatile unsigned char key_val;
static struct fasync_struct *button_async;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned char pinval;
printk("irq = %d
", irq);
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval)
{
/* 松开 */
key_val = pindesc->key_val;
}
else
{
/* 按下 */
key_val = 0x80|pindesc->key_val;
}
//button_async这个结构中包含应用程序的pid号
kill_fasync (&button_async, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
static int fifth_chrdev_open(struct inode *inode, struct file *file)
{
printk("fifth_chrdev_open
");
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
static ssize_t fifth_chrdev_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
if (count != 1)
return -EINVAL;
copy_to_user(buf, &key_val, 1);
return 1;
}
int fifth_chrdev_release(struct inode *inode, struct file *file)
{
printk("fifth_chrdev_release
");
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
return 0;
}
//当应用程序调用这个接口“fcntl(fd, SETFL, oflags | FASYNC)”时,函数“fasync_helper”就会被调用
static int fifth_chrdev_fasync(int fd, struct file *filp, int on)
{
printk("fifth_chrdev_fasync
");
//这个函数获取应用程序的pid并放入(button_async)这个结构
return fasync_helper(fd, filp, on, &button_async);
}
static struct file_operations fifth_chrdev_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = fifth_chrdev_open,
.read = fifth_chrdev_read,
.release = fifth_chrdev_release,
.fasync = fifth_chrdev_fasync,
};
int major;
static int fifth_chrdev_init(void)
{
major = register_chrdev(0, "fifth_chrdev", &fifth_chrdev_fops); //注册
fifth_chrdev_class = class_create(THIS_MODULE, "fifthchrdev");
fifth_chrdev_class_dev = class_device_create(fifth_chrdev_class, NULL, MKDEV(major, 0), NULL, "fifth_chrdev");
return 0;
}
static void fifth_chrdev_exit(void)
{
unregister_chrdev(major, "fifth_chrdev"); //卸载
class_device_unregister(fifth_chrdev_class_dev);
class_destroy(fifth_chrdev_class);
}
module_init(fifth_chrdev_init);
module_exit(fifth_chrdev_exit);
MODULE_LICENSE("GPL");
异步通知测试程序(fifth_chrdev_test.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
static int fd;
void my_signal_fun(int signum)
{
unsigned char key_val;
read(fd, &key_val, 1);
printf("key_val = 0x%x
", key_val);
}
int main(int argc, char **argv)
{
volatile unsigned char key_val;
signal(SIGIO, my_signal_fun);
int Oflags;
fd = open("/dev/fifth_chrdev",O_RDWR);
if(fd < 0)
{
printf("can't open!
");
return -1;
}
fcntl(fd, F_SETOWN, getpid());
Oflags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, Oflags | FASYNC);
while(1)
{
sleep(1000);
}
return 0;
}
测试:
- 修改Makefile
- 编译并拷贝到(first_fs)目录
- 加载模块(insmod ./fifth_chrdev.ko)
- 运行测试程序(./fifth_chrdev_test)
所占资源:
九、 字符设备驱动程序之同步互斥阻塞(第十一课)
目的:同一时刻,只能有一个APP来打开同一个驱动程序
方法一:用(canopen)变量做一个标记
第一步:设置一个静态的全局变量
第二步:一旦打开该驱动就让这个变量自减,自减后若等于0表示没有打开过,可以继续往下走;若有人打开过了,则变量自减之后等于负1,就会进入if里面把变量自加之后返回 "EBUSY"。
第三步:在关闭这个设备的时候,在函数内让变量加1即可。
但实质上有漏洞:因为我们linux是多任务的系统
Linux是多任务系统,A程序执行过程中,有可能被切换成B程序执行这种情况。
从汇编的角度看"canopen--",其实是被分成了3步去执行:1.读出,2.修改,3.写回。在这个分部执行的过程中很有可能中间会被切换出去
解决办法:使用原子操作
方法二:原子操作
原子操作指的是在执行过程中不会被别的代码路径所中断的操作。
常用原子操作函数举例:
atomic_t v = ATOMIC_INIT(0); //定义原子变量v并初始化为0
atomic_read(atomic_t *v); //返回原子变量的值
void atomic_inc(atomic_t *v); //原子变量增加1
void atomic_dec(atomic_t *v); //原子变量减少1
int atomic_dec_and_test(atomic_t *v); //自减操作后测试其是否为0,为0则返回true,否则返回false。
第一步:修改代码,将 "canopen" 变量定义成"原子" 变量
第二步:把打开设备文件函数的判断语句改为原子的测试函数
第三步:把关闭设备文件函数的变量自加改为原子自加
这样就不会发生"A程序执行中被B程序切换出去"的情况了,原子操作中间是不会被打断的。
测试:
在同一时刻,只有一个应用程序可以打同一个驱动程序,其余应用程序就无法打开这个驱动程序了
方法三:信号量
信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。当获取不到信号量时,进程进入休眠等待状态。
在操作之前要申请(获取)信号量,若申请不到要么就返回要么就休眠等待;若申请到了就可以继续往下操作,操作完了之后得释放掉信号量,这时若有其他应用程序在等待信号量,就会去唤醒哪个应用程序。
定义信号量:
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);//初始化为0
static DECLARE_MUTEX(button_lock); //定义互斥锁,也可以用以上3行代码自己定义然后初始化
获得信号量:
void down(struct semaphore * sem);//第二次无法获取到信号量的就会在这里陷入休眠,只有第一个应用程序去释放掉信号量的时候才会被唤醒,这个将死的程序不能被杀死(如果杀死这个将死程序,当杀死一个应用程序的时候,这两个程序都会被杀死)
int down_interruptible(struct semaphore * sem);//若获取不到信号量就会休眠,但是这个休眠状态是可以被打断的,可以被结束掉
int down_trylock(struct semaphore * sem);//试图取获取信号量,若获取不到就会返回,就不会陷入休眠
释放信号量:
void up(struct semaphore * sem);
第一步:定义并初始化一个互斥变量
这个宏会定义和初始化这个变量
第二步:在驱动程序的open函数中获取信号量
第三步:在驱动程序的release函数中释放信号量
测试:
第一次运行时应用程序调用open会获取一次信号量
再次执行应用程序调用同一个驱动程序时会处于将死(D)状态
方法四:阻塞(O_RDWR)和非阻塞(O_NONBLOCK)[默认为阻塞]
阻塞操作:是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
比如读取一个按键值,当前没有按键按下的话,那么就一直等待按键被按下了才返回。
非阻塞操作:进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
比如读取一个按键值当前没有按键值可读取也立刻返回,单返回一个错误。
格式:fd = open("设备名称", O_RDWR | O_NONBLOCK);//非阻塞
在应用程序是通过open设备时,若传入的参数 "| O_NONBLOCK" 就是非阻塞操作,若不传入这个标记时,默认为阻塞操作。
那么,对于阻塞或者非阻塞驱动程序会进行处理,这个"O_NONBLOCK"标记在上面的"file"这个变量中获取。这个结构时内核提供的。
在驱动程序的open函数中判断阻塞方式
在驱动程序的read函数中也得判断阻塞方式
驱动程序(sixth_chrdev.c)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
/*
* eint 0 GPF0
* eint 2 GPF2
* eint 11 GPG3
* eint 19 GPG11
*/
static struct class *sixth_chrdev_class;
static struct class_device *sixth_chrdev_class_dev;
static volatile unsigned char key_val;
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
/* 中断事件标志, 中断服务程序将它置1,s3c24xx_buttons_read将它清0 */
static volatile int ev_press = 0;
static DECLARE_MUTEX(button_lock); //定义互斥锁
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[4] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
{S3C2410_GPG11, 0x04},
};
static irqreturn_t button_irq(int irq, void *dev_id)
{
struct pin_desc *pindesc = (struct pin_desc *)dev_id;
unsigned char pinval;
//printk("irq = %d
", irq);
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(pinval)
{
/* 松开 */
key_val = pindesc->key_val;
}
else
{
/* 按下 */
key_val = 0x80|pindesc->key_val;
}
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
ev_press = 1;
return IRQ_HANDLED;
}
static int sixth_chrdev_open(struct inode *inode, struct file *file)
{
printk("sixth_chrdev_open
");
if(file->f_flags & O_NONBLOCK)
{
if (down_trylock(&button_lock))
return -EBUSY;
}
else
{
/* 获取信号量 */
down(&button_lock);
}
request_irq(IRQ_EINT0, button_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, button_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, button_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, button_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;
}
static ssize_t sixth_chrdev_read(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
if (count != 1)
return -EINVAL;
if(file->f_flags & O_NONBLOCK)
{
if (!ev_press)
return -EAGAIN;
}
else
{
/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);
}
copy_to_user(buf, &key_val, 1);
ev_press = 0;
return 1;
}
int sixth_chrdev_release(struct inode *inode, struct file *file)
{
printk("sixth_chrdev_release
");
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
free_irq(IRQ_EINT19, &pins_desc[3]);
up(&button_lock);
return 0;
}
static struct file_operations sixth_chrdev_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = sixth_chrdev_open,
.read = sixth_chrdev_read,
.release = sixth_chrdev_release,
};
int major;
static int sixth_chrdev_init(void)
{
major = register_chrdev(0, "sixth_chrdev", &sixth_chrdev_fops); //注册
sixth_chrdev_class = class_create(THIS_MODULE, "sixthchrdev");
sixth_chrdev_class_dev = class_device_create(sixth_chrdev_class, NULL, MKDEV(major, 0), NULL, "sixth_chrdev");
return 0;
}
static void sixth_chrdev_exit(void)
{
unregister_chrdev(major, "sixth_chrdev"); //卸载
class_device_unregister(sixth_chrdev_class_dev);
class_destroy(sixth_chrdev_class);
}
module_init(sixth_chrdev_init);
module_exit(sixth_chrdev_exit);
MODULE_LICENSE("GPL");
测试程序(sixth_chrdev_test.c)
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
int fd;
int main(int argc, char **argv)
{
volatile unsigned char key_val;
int ret;
fd = open("/dev/sixth_chrdev",O_RDWR | O_NONBLOCK);//非阻塞:O_NONBLOCK
if(fd < 0)
{
printf("can't open!
");
return -1;
}
while(1)
{
ret = read(fd, &key_val, 1);
printf("ret : %d, key_val = 0x%x
", ret, key_val);
sleep(5);
}
return 0;
}
测试(非阻塞):
第一次运行应用程序时会成功获取到信号量,但是会不停的读取按键值,若没有按键按下则返回"-EAGAIN"
第二次运行应用程序就会无法打开
<wiz_tmp_tag id="wiz-table-range-border" contenteditable="false" style="display: none;">