linux io 阻塞,非阻塞,同步,异步

阻塞后的进程会休眠被移出调度器调度队列,如果休眠的进程不唤醒,将会一直休眠。

非阻塞的进程会一直轮询设备资源,直到可用就使用相应的资源,不可用就执行其他,用于可以让程序自己处理不满足的情况。



应用层:

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:

static unsigned int globalfifo_poll(struct file *filp, poll_table * wait)
{
unsigned int mask = 0;
static char flag = 0 ;
struct globalfifo_dev *dev = filp->private_data;

mutex_lock(&dev->mutex);

printk(KERN_INFO "globalfifo awake in  place 1.......\n");
poll_wait(filp, &dev->r_wait, wait);

poll_wait(filp, &dev->w_wait, wait);
printk(KERN_INFO "globalfifo awake in  place 2.......\n");

if (dev->current_len == 2) {
mask |= POLLOUT | POLLWRNORM;
}

if ((dev->current_len == 6) && (flag == 0)){
mask |= POLLOUT | POLLWRNORM;
flag++;
}
printk(KERN_INFO "mask is %d.......\n", mask);


mutex_unlock(&dev->mutex);
return mask;
}

内核打印信息如下:

[52543.418092] globalfifo awake in  place 1.......
[52543.418094] globalfifo awake in  place 2.......
[52543.418095] mask is 260.......
[52543.418098] globalfifo awake in  place 1.......
[52543.418100] globalfifo awake in  place 2.......
[52543.418101] mask is 260.......

开始休眠:
echo 'h' > /dev/globalfifo
echo 'h' > /dev/globalfifo
[52543.432873] written 2 bytes(s),current_len:4   
[52543.436149] globalfifo awake in  place 1....... 
[52543.436154] globalfifo awake in  place 2.......
[52543.436159] mask is 0.......

唤醒
echo 'h' > /dev/globalfifo
[52569.069991] written 2 bytes(s),current_len:6
[52569.072530] globalfifo awake in  place 1.......
[52569.072536] globalfifo awake in  place 2.......
[52569.072540] mask is 260.......

休眠
[52569.072570] globalfifo awake in  place 1.......
[52569.072574] globalfifo awake in  place 2.......
[52569.072578] mask is 0.......


唤醒后
1. 应用调用驱动层poll -->
       2. 执行驱动xxx_poll 函数unsigned int mask = 0;
            3.  poll_wait 中的进程被唤醒
                        打印:
                         globalfifo awake in  place 1.......
                         globalfifo awake in  place 2.......

                  4.  return mask = 260

                       应用select()到可写,在while(1) 条件下继续select

                        5. 调用驱动层poll -->

                                6. 执行驱动xxx_poll 函数unsigned int mask = 0;

                                        7.  poll_wait 中的进程被唤醒

                        打印:

                                                     globalfifo awake in  place 1.......
                                                     globalfifo awake in  place 2.......

                                               8. return mask = 0

                                                    应用层select 休眠进程


-----------------------------------------------------------------------------------------------

select() 用法
用于监视设备的状态,可读,可写,或其他状态
note:要将需要监视的文件句柄加入相应的文件描述符集合
如:
FD_SET(FD1, &rdfds);/* 分别把3个句柄加入读监视集合里去 */
FD_SET(FD2, &rdfds);
FD_SET(FD3, &rdfds);
........
select(MAXFD+1, &rdfds, NULL, NULL, &tv); /* 注意是最大值还要加1 */

例子:
int sa, sb, sc;
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设备驱动开发详解&互联网


你可能感兴趣的:(linux设备驱动)