阻塞后的进程会休眠被移出调度器调度队列,如果休眠的进程不唤醒,将会一直休眠。
非阻塞的进程会一直轮询设备资源,直到可用就使用相应的资源,不可用就执行其他,用于可以让程序自己处理不满足的情况。
应用层:
fd = open("/dev/ttyS1",O_RDWR); // 阻塞
fd = open("/dev/ttyS1",O_RDWR| O_NONBLOCK);// 非阻塞
note:
可使用ioctl(), fcntl() 改变读写方式
如:fcntl(fd, F_SETFL, O_NONBLOCK)将fd的i/o设置为非阻塞。
----------------------------------------------------------------------------------------------------------------
阻塞实例:
Note: 应用和设备都需要相应的实现,在设备驱动端把读取他的应用的进程休眠掉,
并在适当的条件成立时唤醒这个进程。
app:
char buf;
fd = open("/dev/ttyS1",O_RDWR); // 阻塞
.....
res = read(fd, &buf, 1); // 如果IO设备不可获得则开始休眠(在设备驱动中实现)
if(res == 1)
printf("%c\n", buf);
driver: (参考:net\bluetooth\Af_bluetooth.c)
static ssize_t xxx_write (....)
{
.....
DECLARE_WAITQUEUE(wait, current); // 定义等待队列元素
add_wait_queue(xxx_wait, &wait); // 添加元素到等待队列
do{
avail = device_writable(....);
if(avail <0){
if(file ->f_flags &O_NONBLOCK){ // 非阻塞
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE); // 改变进程状态
schedule(); // 调度其他进程执行,执行到这里开始休眠 *************
if(signal_pending(current)){ // 唤醒后第一时间判断是否是信号唤醒****************
ret = -ERESTARTSYS;
goto out;
}
}
}while(avail<0);
device_write(...)
out :
remove_wait_queue(&xxx_wait, &wait); // 将元素移出xxx_wait指向的队列
set_current_state(TASK_RUNNING); // 设置进程状态为 TASK_RUNNING
return ret;
}
其他进程:
wake_up_interruptible(xxx_wait); // 唤醒xxx_wait进程
设备轮询:
应用层的select() 或 poll() 会监视设备fd的状态,首先会对所有的fd进行状态获得若
任一资源可用则直接返回,若都不可用,则select() 或 poll()会将进程休眠,直到资源可读。
select() 或 poll() 会调用驱动中的poll(); poll()要实现 poll_wait(); 使select() 或 poll()被休眠的
进程能被唤醒。
应用层:
void main(void)
{
int fd, num;
char rd_ch[BUFFER_LEN];
fd_set rfds, wfds; /* 读/写文件描述符集 */
/* 以非阻塞方式打开/dev/globalfifo设备文件 */
fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
if (fd != -1) {
/* FIFO清0 */
if (ioctl(fd, FIFO_CLEAR, 0) < 0)
printf("ioctl command failed\n");
while (1) {
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)) // 判断描述符是否被置位
printf("Poll monitor:can be read\n");
/* 数据可写入 */
if (FD_ISSET(fd, &wfds))
printf("Poll monitor:can be written\n");
}
} else {
printf("Device open failure\n");
}
}
驱动层:poll() (参考:net\bluetooth\Af_bluetooth.c)
static unsigned int xxx_poll(struct file *filp, poll_table * wait)
{
unsigned int mask = 0;
struct globalfifo_dev *dev = filp->private_data;
mutex_lock(&dev->mutex);
poll_wait(filp, &dev->r_wait, wait);
poll_wait(filp, &dev->w_wait, wait); // 作用是让等待队列能唤醒进程
if (dev->current_len != 0) {
mask |= POLLIN | POLLRDNORM;
}
if (dev->current_len != GLOBALFIFO_SIZE) {
mask |= POLLOUT | POLLWRNORM;
}
mutex_unlock(&dev->mutex);
return mask;
}
------------------------------------------------------------------------------------------------------------
poll 分析
驱动poll:
内核打印信息如下:
4. return mask = 260
应用select()到可写,在while(1) 条件下继续select
5. 调用驱动层poll -->
6. 执行驱动xxx_poll 函数unsigned int mask = 0;
打印:
8. return mask = 0
应用层select 休眠进程
FD_SET(FD2, &rdfds);
FD_SET(FD3, &rdfds);
sa = socket(...); /* 分别创建3个句柄并连接到服务器上 */
connect(sa,...);
sb = socket(...);
connect(sb,...);
sc = socket(...);
connect(sc,...);
FD_SET(sa, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
FD_SET(sb, &rdfds);
FD_SET(sc, &rdfds);
在使用select函数之前,一定要找到3个句柄中的最大值是哪个,我们一般定义一个变量来保存最大值,取得最大socket值如下:
int maxfd = 0;
if(sa > maxfd) maxfd = sa;
if(sb > maxfd) maxfd = sb;
if(sc > maxfd) maxfd = sc;
然后调用select函数:
ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
同样的道理,如果我们要检测用户是否按了键盘进行输入,我们就应该把标准输入0这个句柄放到select里来检测,如下:
FD_ZERO(&rdfds);
FD_SET(0, &rdfds);
tv.tv_sec = 1;
tv.tv_usec = 0;
ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */
if(ret < 0) perror("select");/* 出错 */
else if(ret == 0) printf("超时\n"); /* 在我们设定的时间tv内,用户没有按键盘 */
else { /* 用户有按键盘,要读取用户的输入 */
scanf("%s", buf);
}
参考:linux设备驱动开发详解&互联网