我本仁慈,奈何苍天不许
分类: LINUX
2013-12-26 16:54:47
Linxu设备驱动中的阻塞与非阻塞I/O
轮询的缺点:
1、效率低
2、CPU负担重
中断的优点:
1、减轻CPU负担
2、实时性好
{//?驱动中阻塞和非阻塞,异步通知是什么
----------
| | --------
| 进程 | <-------------->| 设备 |
| | --------
----------
阻塞: 进程等待设备资源可用, 等待过程中, 进程休眠(CPU切换去执行别的进程)。
非阻塞: 不等待,轮询设备
异步通知: 让设备主动去通知进程。本质就是中断
故和CPU打交道的有阻塞、非组赛、中断
注: 提出阻塞,非阻塞,异步通知概念,这里是为了解决 进程和设备间是如何打交道的
}
{//?如何在驱动中实现阻塞I/O
当有多个进程阻塞睡眠时,可用排队的方式,当造成阻塞的条件消失(如有资源释放了),去唤醒阻塞在排队的进程.
{//?什么是等待队列
以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问。
如:
当某进程不满足某条件时(如资源), 可把进程暂时睡眠,放入队列中,
等条件满足时(如某资源被释放出来了),再把进程唤醒去执行
}
{//?如何使用等待队列,在驱动中实现阻塞
#cd /root/driver_example/waitqueue
#make clean
#make
#insmod hello.ko
#mknod /dev/hello c 250 0
#cat /dev/hello
打开另一终端
#echo hello > /dev/hello
可以发现在前面终端会打印出hello
#rmmod hello
{//---driver_example/waitqueue/hello.c
static wait_queue_head_t queue; //定义等待队列
static char *gbuf=NULL;
static int glen=0;
static ssize_t hello_read (struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
/*
wait_event_interruptible 内部有一for循环,
1. 当条件满足时,且无信号需处理时, 跳出循环,返回0
2. 如果有信号未处理,返回非零值
return -ERESTARTSYS,
系统调用看到ERESTARTSYS,则在处理完信号后,又会再次触发前次的调用(即又调用了一次hello_read)
3. 当条件不满足时,且无信号需处理,则睡眠等待
*/
if (wait_event_interruptible(queue, glen!=0)) //把调用该函数的进程(cat /dev/hello), 放入等待队列queue中,等
{
return -ERESTARTSYS; //表示信号函数处理完毕后重新执行信号函数前的某个系统调用.
//即等待队列在等待中,如果有信号,先执行完信号后,再执行一次系统调用,继续等待
}
glen =0;
...
}
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
glen = count;
wake_up_interruptible(&queue); //唤醒在queue等待队列上等待的所有进程 , 这里指 cat /dev/hello 进程
...
}
static int hello_init(void)
{
gbuf = kmalloc(GBUF_MAX, GFP_KERNEL);
init_waitqueue_head(&queue);
...
}
}
}
}
{//?如何在驱动中实非阻塞I/O
应用:
用select/poll // I/O多路复用
驱动:
实现.poll 函数。
---------------------------
| 应用
| select/poll
---------------------------
|
---------------------------
|内核 sys_poll //poll系统调用 ,轮询poll_table中的驱动.poll, 当设备可读或可写则返回
| |
| .poll //驱动实现的.poll
---------------------------
/*原理:
select/poll 会调用 sys_poll(系统调用) ,再调用驱动中的.poll
sys_poll它会循环监听 poll_table上的设备是否就绪(由其关联的等待队列queue触发),如果就绪则返回
如不就绪,则 schedule (让渡内核去执行别的进程), 返回后继续监测。
如果发生阻塞,会发生在sys_poll中,
*/
{//------实验 poll------------
#cd /root/driver_example/poll
#make clean
#make
#insmod hello.ko
#mknod /dev/hello c 250 0
#gcc test.c
#./a.out 等待有数据输入
打开另一终端
#echo hello > /dev/hello //写入数据, 原终端显示Poll monitor:can be read
#cat /dev/hello //读走内容,原终端等待有数据输入
}
{//---poll/test.c //功能: 查询设备,看设备是否可读写
#include
#include
#include
#include
#include
#include
int main()
{
int fd, num;
fd_set rfds,wfds;
fd = open("/dev/hello", O_RDONLY);
if (fd < 0)
{
printf("Device open failure\n");
}
while (1)
{
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd, &rfds);
FD_SET(fd, &wfds);
select(fd + 1, &rfds , &wfds, NULL, NULL); // I/O多路复用 最终调用驱动的.poll 函数
select在可读或者可写时,不阻塞,否则阻塞,但是真正的阻塞并不是在这,而是在系统调用的sys_poll中
//数据可获得
if (FD_ISSET(fd, &rfds))
{
printf("Poll monitor:can be read\n");
}
//数据可写入
if (FD_ISSET(fd, &wfds))
{
printf("Poll monitor:can be write\n");
}
}
}
}
{//---poll/hello.c
#include
static unsigned int hello_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(filp, &queue, wait);// 把当前进程添加到wait(poll_table 设备监控列表)中,并关联相应的等待队列queue
//当有多个驱动添加到poll_table,表示可以同时监控多个驱动设备的变化
//poll_wait函数并不阻塞, 真正的阻塞动作是在poll系统调用中完成
if (glen != 0)
{
mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/
}
if (glen < GBUF_MAX)
{
mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/
}
return mask; //通过立即返回标志
}
static struct file_operations hello_fops = {
.poll = hello_poll,
};
}
}
{//?如何在驱动中实现异步通知
#cd /root/driver_example/async
#make clean
#make
#insmod hello.ko
#mknod /dev/hello c 250 0
#gcc test.c
#./a.out //等待中
打开另一终端
#echo hello > /dev/hello //写数据, 触发异步信号
//原终端会显示receive a signal from globalfifo,signalnum:29
{//--/async/test.c
#include
#include
#include
#include
#include
#include
void input_handler(int signum) //接收到异步读信号后的动作
{
printf("receive a signal from globalfifo,signalnum:%d\n",signum);
}
int main()
{
int fd = 0;
int oflags;
fd = open("/dev/hello",O_RDWR, S_IRUSR | S_IWUSR);
if (fd < 0)
{
printf("Device open failure\n");
}
//启动信号驱动机制
signal(SIGIO, input_handler); //让input_handler()处理SIGIO信号
fcntl(fd, F_SETOWN, getpid()); //设置设备文件的所有者为本进程
oflags = fcntl(fd, F_GETFL); // 会调用 驱动中的 .fasync
fcntl(fd, F_SETFL, oflags | FASYNC); //FASYNC 设置支持异步通知模式
while (1)
{
sleep(100);
}
}
}
{//--/async/hello.c
struct fasync_struct *async_queue; /* 异步结构体指针,用于读 */
static ssize_t hello_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
kill_fasync(&async_queue, SIGIO, POLL_IN); //发送SIGIO 异步通知信号
return count;
}
static int hello_fasync(int fd, struct file *filp, int mode)
{
return fasync_helper(fd, filp, mode, &async_queue); //处理标志的变更
}
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.write = hello_write,
.fasync = hello_fasync,
};
}
}