null上章回顾上章回顾从并发的需要,引起的竞争状态,到解决竞争状态的方法机制:
禁止中断
信号量
自旋锁
Completion
原子操作
阻塞和非阻塞型I/O 阻塞和非阻塞型I/O第4章本章目标本章目标
掌握进程睡眠和唤醒的方法
掌握阻塞型I/O的实现方法
掌握poll/select系统调用的实现方法
本章结构本章结构阻塞型I/O 阻塞和非阻塞型I/O异步通知 非阻塞I/O 非阻塞I/Opoll/select进程休眠和唤醒方法 阻塞型I/O实例 4-1 阻塞型I/O4-1 阻塞型I/O当用户程序调用read函数时,驱动程序的read并没有准备好数据,怎么办?当用户程序调用write时,驱动程序的write的缓冲区已满,此时怎么办?1.用户程序不会管理这些问题。
2.驱动程序默认情况下,阻塞该进程。将其置入休眠状态直到请求可继续
3. 直接返回,这是非阻塞IO
4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法休眠的意义?
从调度器的运行队列某个等待队列
等到某个事件发生,在从等待队列返回到运行队列。
如何将进程安全的进入休眠状态?
不能在原子上下文进行休眠:
休眠时,对外界一无所知,进程必须重新检测等待条件
进程只有确保会被其他进程唤醒,才能进入休眠
需要等待队列来管理和维护这些信息:
等待队列就是一个进程链表,其中包含了等待某个特定事件的所有进程4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法等待队列的作用
实现阻塞进程的唤醒
实现内核中的异步事件通知机制
队列的数据结构
系统的调度机制
同步系统资源的访问
Semaphore可以用wait queue来实现
4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法
等待队列通过”等待队列头”来管理
Struct wait_queue_head_t
DECLARE_WAIT_QUEUE_HEAD(name);wait_queue_head_t xxx_queue;
init_waitqueue_head(&xxx_queue);4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法等待事件
在休眠的同时,也检查进程等待的条件
休眠函数wait_event(queue,condition);
wait_event_interruptible(queue,condition);wait_event_timeout(queue,condition,timeout);
wait_event_interruptible_timeout(queue, condition,timeout);休眠前后都要对该表达式求值;在条件为真之前,进程会保持休眠等待队列头,通过”值传递”等待限定的时间4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法唤醒函数
wake_up(wait_queue_head_t *queue);
wake_up_interruptible(wait_queue_head_t *queue);约定的做法是成对使用wake_up_nr(wait_queue_head_t *queue,int nr);
wake_up_interruptible_nr(wait_queue_head_t *queue,int nr);唤醒nr各独占等待进程,而不是一个,当nr=0时,唤醒所有的独占等待进程。wake_up_all(wait_queue_head_t *queue);
wake_up_interruptible_all(wait_queue_head_t *queue);wake_up_interruptible_sync(wait_queue_head_t *queue);4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
ssize_t sleepy_read(struct file *filp,char __user *buf,
size_t count,loff t *pos)
{
printk(KERN_DEBUG “process %i (%s) going to sleep\n”,
current->pid,current->comm);
wait_event_interruptible(wq,flag != 0);
flag = 0;
printk(KERN_DEBUG “awoken %i (%s)\n”, current->pid,current->comm);
return 0;
}
定义并初始化wait_queue_head_t 打印出是哪个进程调用驱动程序。在调用前后都要检查condition,如果满足条件,就不再休眠,否则进入休眠状态4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法ssize_t sleepy_write(struct file *filp, const char __user *buf,
size_t count,loff_t *pos)
{
printk(KERN_DEBUG “process %i (%s) awakening the readers…\n”,
current->pid,current->comm);
flag = 1;
wake_up_interruptible(&wq);
return count;
}
察看那个进程调用驱动的write函数唤醒休眠在wq队列上的所有的进程。4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法上面实例中存在一个概率很小的竟态条件
A,B 进程都等在wq的队列上
C进程调用wake_up_interruptible
A进程被唤醒,检查条件flag !=0 成立
此时,调度到B进程
B进程也检查到flag != 0成立,
这样,一个事件唤醒了 两 个进程,可能产生竞 态
可以用原子操作防止这种情况 4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法设置进程休眠的内部细节
分配并初始化一个wait_queue_t结构
包含休眠进程的信息,以及期望被唤醒的相关细节
设置进程的状态,将其标记为休眠状态
TASK_INTERRUTIBLE
TASK_UNINTERRUPTIBLE
让出处理器
void set_current_state(int new_state);current->state=TASK_INTERRUPTIBLE;if(!condition)
schedule();对应于两种休眠状态新旧函数操作进程状态4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法手工休眠
早期版本的方法
读者可以自行了解
唤醒等待队列可能发生的情况
当调用wake_up时,所有等待在该队列上的进程都被唤醒,并进入可运行状态
如果只有一个进程可获得资源,此时,其他的进程又将再次进入休眠
如果数量很大,被称为”疯狂兽群”4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法独占等待
与普通休眠的不同
等待队列入口设置了WQ_FLAG_EXCLUSIVE标志时,则会被添加到等待队列的尾部。而没有这个标志的入口会被添加到头部。
在某个等待队列上调用wake_up时,它会在唤醒第一个具有WQ_FLAG_EXCLUSIVE标志的进程之后停止唤醒其他进程。
使进程进入独占等待函数:
void prepare_to_wait_exclusive(wait_queue_head_t *queue
wait_queue_t *wait,int state);使用wait_event及其变种无法使用独占等待4-1-1进程休眠和唤醒方法4-1-1进程休眠和唤醒方法其他休眠调用
void sleep_on(wait_queue_head_t *queue);
void interruptible_sleep_on(wait_queue_head_t *queue);
4-1-2阻塞型IO实例4-1-2阻塞型IO实例struct xxx_pipe{
wait_queue_head_t inq,outq; /*读取和写入队列*/
char *buffer,*end; /*缓冲区的起始和结尾*/
int buffersize; /*用于指针计算*/
char *rp,*wp; /*读取和写入的位置*/
int nreaders,nwriters; /*用于读写打开的数量*/
struct fasync_struct *async_queue;/*异步读取者*/
struct semaphore sem; /*互斥信号量*/
struct cdev cdev; /*字符设备结构*/
};
4-1-2阻塞型IO实例4-1-2阻塞型IO实例static ssize_t xxx_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)
{
struct xxx_pipe *dev=filp->private_data;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
while(dev->rp == dev->wp) /*缓冲区没有准备好,无数据可读取*/
{up(&dev->sem);/*进入休眠之前,释放信号量*/
if(filp->f-flags & O_NONBLOCK)
return -EAGAIN;
if(wait_event_interruptible(dev->inq,(dev->rp!=dev->wp))
return -ERESTARTSYS;/*此时应该有进程唤醒,交给内核上层处理*/
/*否则循环,但首先获取锁,防止被其他竞争者拿着数据*/
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
}4-1-2阻塞型IO实例4-1-2阻塞型IO实例/*数据已就绪,返回*/
if(dev->wp > dev->rp)
count=min(count,(size_t)(dev->wp - dev->rp));
else /*写入指针回卷,返回数据直到dev->end*/
count=min(count,(size_t)(dev->end – dev->rp));
if(copy_to_user(buf,dev->rp,count))
{ up(&dev->sem);
return -EFAULT;
}
dev->rp += count:
if(dev->rp == dev->end)
dev->rp = dev->buffer;/*回卷*/
up(&dev->sem);
/*最后,唤醒所有写入者并返回*/
wake_up_interruptible(&dev->outq);
return count;
} 阶段总结阶段总结介绍了阻塞型I/O的实现方法
介绍了进程休眠和唤醒的方法,重点介绍了wait_event和wake_up
4-2 非阻塞I/O4-2 非阻塞I/O调用进程显式的指明不想阻塞
设置filp->f_flag |= O_NONBLOCK
int fd;
char buf;
fd = open(“/dev/ttyS1”,O_RDWR | O_NONBLOCK);
…
while(read(fd, &buf, 1) != 1);/*串口上无输入也返回,所以要循环尝试读取串口*/
printf(“%c\n”,buf);
4-2-1 poll和select4-2-1 poll和selectPoll、select、epoll 都允许进程决定是否可以对一个或多个打开的文件做非阻塞的读取或写入
这些调用也会阻塞进程,直到给定的文件描述符集合中的任何一个可读取或写入
常常用于那些要使用多个输入或输出流而又不会阻塞于其中任何一个流的应用程序中
4-2-1 poll和select4-2-1 poll和select都是调用驱动程序的poll来实现的
在一个或多个可指示poll状态变化的等待队列上调用poll_wait
如果当前没有文件描述符可用来执行I/0,则内核将使进程在传递到该系统调用的所有文件描述符对应的等待队列上等待。
返回一个用来描述操作是否可以立即无阻塞执行的位掩码unsigned int (*poll)(struct file *filp,poll_table *wait);
中声明,驱动程序不需要了解该结构的细节void poll_wait(struct file *,wait_queue_head_t *,poll_table *);向poll_table添加一个等待队列返回可以立即执行操作的位掩码4-2-1 poll和select4-2-1 poll和select static unsigned int xxx_poll(struct file *filp,poll_table *wait)
{
struct xxx_pipe *dev = filp->private_data;
unsigned int mask=0;
down(&dev->sem);
poll_wait(filp,&dev->inq,wait);
poll_wait(filp,&dev->outq,wait);
if(read_buffer_not_empty) //如果接收buffer不为空,可读
mask |= POLLIN | POLLRDNORM; /*可读取*/
if(write_buffer_not_full) //如果写buffer不满,可写
mask |= POLLOUT | POLLWRNORM; /*可写入*/
up(&dev->sem);
return mask;
}增加两个等待队列到poll_table中4-2-1 poll和select4-2-1 poll和select调用poll和select的目的是:
确定接下来的操作是否是阻塞型的
使应用程序可以同时等待多个数据流
从设备读取数据
read可以返回比请求少的数据
如果设置了O_NONBLOCK, 在没有数据时,立即返回。此时poll会
报告
软件系统测试报告下载sgs报告如何下载关于路面塌陷情况报告535n,sgs报告怎么下载竣工报告下载
设备不可读。
如果到达文件尾,poll会报告POLLUP.
向设备些数据
写缓冲区有空间,poll应该报告设备可写
写缓冲区已满,poll应该报告设备不可写
永远不要让write调用在返回前等待数据的传输结束,即使O_NONBLOCK标志被清除。
4-2-1 poll和select4-2-1 poll和selectselect()系统调用的原型如下:
文件描述符集合相关函数:
int select ( int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);struct timeval
{
int tv_sec; /*秒*/
int tv_usec; /*微妙*/
};
FD_ZERO(fd_set *set);FD_SET(int fd, fd_set *set);FD_CLR(int fd, fd_set *set);FD_ISSET(int fd, fd_set *set);清除一个文件描述符集将一个文件描述符加入文件描述符集中将一个文件描述符从文件描述符集中清除。判断文件描述符是否被置位 4-2 非阻塞I/O4-2 非阻塞I/O int fd;
fd_set rfds,wfds;//读/写文件描述符集
/*以非阻塞方式打开/dev/xxx设备文件*/
fd = open(“/dev/xxx”, O_RDWR | O_NONBLOCK);
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(fd, &rfds);
FD_SET(fd, &wfds);
select(fd + 1, &rfds, &wfds, NULL, NULL);
/*数据可获得*/
if(FD_ISSET(fd, &rfds))
{
//读数据
}
if(FD_ISSET(fd, &wfds))
{
//写数据
}阶段总结阶段总结poll/select在驱动程序中的实现
应用层如何调用驱动中的poll/select本章总结本章总结重点掌握进程休眠和唤醒方法重点掌握poll/select实现方法一般了解阻塞型I/O 阻塞和非阻塞型I/O异步通知 非阻塞I/O 非阻塞I/Opoll/select进程休眠和唤醒方法 阻塞型I/O实例 实验实验任务一、阻塞型字符设备驱动实验