zoukankan      html  css  js  c++  java
  • IO模型

    前言


      网上关于IO模型的博文已经很多了,我认为这篇博文(戳进去)讲的很到位,大家可以参考一下。建议大家把参考的博文研究一下在阅读此博文。下面我就从代码级的角度再次剖析一下这几个IO模型的区别。

      虽然这里参看的代码是内核驱动的代码(ARM6410开发板GPIO驱动),但是我会尽量讲的清楚点。

    阻塞式IO


      关于阻塞式IO的介绍就不再啰嗦了。下面我们就分别从内核角度和用户角度来进行分析。

    1.用户角度


     

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <sys/types.h>
     4 #include <sys/stat.h>
     5 #include <fcntl.h>
     6 #include <unistd.h>
     7 #include <signal.h>
     8 
     9 int main(int argc,char **argv)
    10 {
    11     int fd = open("/dev/cmnin", O_RDONLY);
    12     char key_val[5];
    13    if(fd < 0){
    14         printf("open/dev/cmnin error!
    ");
    15         return 0;
    16    }
    17     int i;
    18    while(1){
    19         sleep(3);
    20         read(fd, key_val, 5);
    21         for(i=0; i<5; i++)
    22             printf("The port %d is %c
    ", i, key_val[i]);
    23         printf("
    ");
    24    }
    25    return 0;
    26 }

      该代码使用的是linux C编写的。在代码中,我们用open函数打开个文件(这是一个设备文件),然后不停的对这个文件进行读操作,并把读到内容打印出来。因为我们在调用open时,没有设置IO方式,所以内核默认为我们是以阻塞的方式进行IO操作的。当文件中没有内容可读时,while循环会直接阻塞在read函数上,直到有数据据时才返回。用户代码就表示上图中的左边部分。

    2.内核角度

      1 #include <linux/module.h>
      2 #include <linux/kernel.h>
      3 #include <linux/fs.h>
      4 #include <linux/init.h>
      5 #include <linux/delay.h>
      6 #include <linux/sched.h>
      7 #include <linux/poll.h>
      8 #include <linux/irq.h>
      9 #include <asm/irq.h>
     10 #include <asm/io.h>
     11 #include <linux/interrupt.h>
     12 #include <asm/uaccess.h>
     13 #include <mach/hardware.h>
     14 #include <linux/platform_device.h>
     15 #include <linux/cdev.h>
     16 #include <linux/miscdevice.h>
     17 
     18 #include <mach/map.h>
     19 #include <mach/gpio.h>
     20 #include <mach/regs-clock.h>
     21 #include <mach/regs-gpio.h>
     22 
     23 
     24 #define DEVICE_NAME        "cmnin"
     25 
     26 struct cmnin_desc {
     27     int gpio;  //端口号
     28     int number; //编号
     29     char *name;        //中断名称
     30     struct timer_list timer; //定时器
     31 };
     32 
     33 static struct cmnin_desc cmnins[] = {
     34     { S5PV210_GPH2(2), 0, "KP_COL2" },
     35     { S5PV210_GPH2(3), 1, "KP_COL3" },
     36     { S5PV210_GPH3(0), 2, "JJK" },
     37     { S5PV210_GPH3(3), 3, "DFU3" },
     38     { S5PV210_GPH3(2), 4, "PTT" },
     39 };
     40 
     41 static volatile char cmnin_values[] = {
     42     '0', '0', '0', '0', '0'
     43 };
     44 
     45 
     46 //等待队列
     47 static DECLARE_WAIT_QUEUE_HEAD(cmnin_waitq);
     48 
     49 //表明key_values数组中是否有数据,0表示无数据可读,1表示有数据可读
     50 static volatile int ev_press = 0;
     51 
     52 
     53 //定时器处理函数,定时器一到时间就会调用该函数
     54 //
     55 static void mini210_cmnins_timer(unsigned long _data)
     56 {
     57     struct cmnin_desc *bdata = (struct cmnin_desc *)_data;
     58     int down;
     59     int number;
     60     unsigned int tmp;
     61 
     62     //获取端口状态
     63     tmp = gpio_get_value(bdata->gpio);
     64 
     65     /* active low */
     66     down = !tmp;
     67     printk("KEY %d: %08x
    ", bdata->number, down);
     68 
     69     number = bdata->number;
     70     if (down != (cmnin_values[number] & 1)) {
     71         cmnin_values[number] = '0' + down;
     72 
     73         ev_press = 1;
     74         wake_up_interruptible(&cmnin_waitq);
     75     }
     76 }
     77 
     78 /**************************cmnin_interrupt()*****************************/
     79 //此中断服务程序,每次中断都重新设置定时器
     80 static irqreturn_t cmnin_interrupt(int irq, void *dev_id)
     81 {
     82 
     83     struct cmnin_desc *bdata = (struct cmnin_desc *)dev_id;
     84 
     85     mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(40));
     86     printk("<0>""interrupt");
     87     return IRQ_HANDLED;
     88 }
     89 
     90 static int mini210_cmnin_open(struct inode *inode, struct file *file)
     91 {
     92     
     93     int irq; 
     94     int i;          //循环变量,五个输入  
     95     int err = 0;  //中断注册函数的返回值
     96 
     97     for (i = 0; i < ARRAY_SIZE(cmnins); i++) {
     98         
     99         //检测设定的端口是否有效
    100         if (!cmnins[i].gpio)
    101             continue;
    102         
    103         //定时器初始化
    104         //第三个参数是给回调函数传入的参数
    105         setup_timer(&cmnins[i].timer, mini210_cmnins_timer,
    106                 (unsigned long)&cmnins[i]);
    107         
    108         //将端口号转换为相应的IRQ值,并赋值给变量irp
    109         irq = gpio_to_irq(cmnins[i].gpio);
    110         
    111         //注册中断服务,上升沿触发中断
    112         err = request_irq(irq, cmnin_interrupt, IRQ_TYPE_EDGE_RISING, 
    113                 cmnins[i].name, (void *)&cmnins[i]);
    114         if (err)
    115             break;
    116     }
    117         
    118     //一旦发现有一个中断申请失败,则放弃中断申请,并将已申请的中断释放
    119     if (err) {
    120         i--;
    121         for (; i >= 0; i--) {
    122             if (!cmnins[i].gpio)
    123                 continue;
    124 
    125             irq = gpio_to_irq(cmnins[i].gpio);
    126             disable_irq(irq);
    127             free_irq(irq, (void *)&cmnins[i]);
    128 
    129             del_timer_sync(&cmnins[i].timer);
    130         }
    131 
    132         return -EBUSY;
    133     }
    134 
    135     ev_press = 1;
    136     return 0;
    137 }
    138 
    139 
    140 static int mini210_cmnin_close(struct inode *inode, struct file *file)
    141 {
    142     int irq, i;
    143 
    144     for (i = 0; i < ARRAY_SIZE(cmnins); i++) {
    145         if (!cmnins[i].gpio)
    146             continue;
    147 
    148         irq = gpio_to_irq(cmnins[i].gpio);
    149         free_irq(irq, (void *)&cmnins[i]);
    150 
    151         del_timer_sync(&cmnins[i].timer);
    152     }
    153 
    154     return 0;
    155 }
    156 
    157 static int mini210_cmnin_read(struct file *filp, char __user *buff,
    158         size_t count, loff_t *offp)
    159 {
    160     unsigned long err;
    161 
    162     if (!ev_press) {
    163         if (filp->f_flags & O_NONBLOCK)
    164             return -EAGAIN;
    165         else
    166             wait_event_interruptible(cmnin_waitq, ev_press);
    167     }
    168 
    169     ev_press = 0;
    170 
    171     //将数据copy到用户空间
    172     err = copy_to_user((void *)buff, (const void *)(&cmnin_values),
    173             min(sizeof(cmnin_values), count));
    174 
    175     return err ? -EFAULT : min(sizeof(cmnin_values), count);
    176 }
    177 
    178 static struct file_operations dev_fops = {
    179     .owner        = THIS_MODULE,
    180     .open        = mini210_cmnin_open,
    181     .release    = mini210_cmnin_close, 
    182     .read        = mini210_cmnin_read,
    183 };
    184 
    185 static struct miscdevice misc = {
    186     .minor        = MISC_DYNAMIC_MINOR, //动态分配设备号
    187     .name        = DEVICE_NAME,
    188     .fops        = &dev_fops,
    189 };
    190 
    191 static int __init cmnin_dev_init(void)
    192 {
    193     int ret;
    194 
    195     //注册设备
    196     ret = misc_register(&misc);
    197     if(ret < 0)
    198     {
    199         printk(DEVICE_NAME " can't register cmnin device
    ");
    200         return ret;
    201     }
    202     printk(DEVICE_NAME"	initialized
    ");
    203     return ret;
    204 }
    205 
    206 static void __exit cmnin_dev_exit(void)
    207 {
    208     misc_deregister(&misc);
    209 }
    210 
    211 module_init(cmnin_dev_init);
    212 module_exit(cmnin_dev_exit);
    213 
    214 MODULE_LICENSE("GPL");
    215 MODULE_AUTHOR("FriendlyARM Inc.");
    View Code

      先说明一下该代码的作用,一个器件(arm6410开发板)上有五个端口,这五个端口的要么的是低电平要么是高电平,在逻辑电路中就是用1(高)和0(低)表示的。有一个用户程序需要知道这几个端口是1还是0。一般情况下,用户程序是不能直接操作硬件设备的,而这部分工作是驱动来实现。该代码的作用就是查看端口的值,并返回给用户程序。

      代码的其它部分就不做介绍了,我们只关心IO问题,所以就看看read是怎么实现的。用户层调用了read函数,而read函数执行者是内核,而内核是通过驱动来管理硬件的。所以最终read函数是由驱动中的mini210_cmnin_read来实现的。

     1 static int mini210_cmnin_read(struct file *filp, char __user *buff,
     2         size_t count, loff_t *offp)
     3 {
     4     unsigned long err;
     5 
     6     if (!ev_press) {
     7         if (filp->f_flags & O_NONBLOCK)
     8             return -EAGAIN;
     9         else
    10             wait_event_interruptible(cmnin_waitq, ev_press);
    11     }
    12 
    13     ev_press = 0;
    14 
    15     //将数据copy到用户空间
    16     err = copy_to_user((void *)buff, (const void *)(&cmnin_values),
    17             min(sizeof(cmnin_values), count));
    18 
    19     return err ? -EFAULT : min(sizeof(cmnin_values), count);
    20 }

      ev_press是一个标志位,当为0的时候表示没有数据(即五个端口的状态没有发生变化,一旦只要有一个发生变化我们就认为产生了数据)。一开始五个端口全为1,ev_press初始值为0。这时候用户调用了read操作,驱动中代码开始执行。en_press=0表示没有,if为真,执行代码 if (filp->f_flags & O_NONBLOCK) 该代码的作用就是查看用户使用的是阻塞式IO还是非阻塞式IO。因为我们使用的阻塞方式,所以执行  wait_event_interruptible(cmnin_waitq, ev_press); 代码。该代码就是在等待一个中断事件的发生,如果事件没发生就一直会处于等待,相当于图中的wait for data部分。当某一时刻,端口状态终于发生了改变,说明有了数据。这时  wait_event_interruptible(cmnin_waitq, ev_press); 终于可以返回了。驱动代码继续执行, copy_to_user 就是将数据copy到用户空间,相当与内核的第二个阶段。return返回是copy用户空间的数量量(单位:字节)。

    3.总结

      从上面的分析中,我们可以看到阻塞式IO其实是阻塞在内核中的某个事件上,等待这个事件反生后,函数才能从内核中返回,而这是数据其实也到了用户空间了。

    2.非阻塞式IO


      非阻塞方式是不管是否有数据,read函数都会直接返回。

      

    1、用户角度

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <signal.h>
    
    int main(int argc,char **argv)
    {
        int fd = open("/dev/cmnin", O_RDONLY|O_NONBLOCK);
        char key_val[5];
        
        if(fd < 0){
          printf("open/dev/cmnin error!
    ");
          return 0;
        }
        int i;
        while(1){
          sleep(3);
            if(read(fd, key_val, 5) >0){
                for(i=0; i<5; i++)
                printf("The port %d is %c
    ", i, key_val[i]);
                printf("
    ");
            }
        }
        return 0;
    }

     代码中,我们调用open函数时,加了O_NONBLOCK关键字,该关键字就是我们要使用非阻塞方式进行IO操作。这时候read函数返回-1表示没有数据,返回>0的数表示读到的数据个数。整个程序会不断的进行死循环操作,对CPU消耗比较大。

    2、内核角度

      内核角度不再进行详细概述,  if (filp->f_flags & O_NONBLOCK) 会为true,所以内核中的read操作,直接会返回-EAGAIN。直到有数据才会执行和阻塞模型相同的操纵。

     3、总结

      非阻塞就是就是但内核中没有数据时,直接返回给用户。

    3.多路复用技术


      多路复用技术需要内核的支持,linux C中的多路复用技术就是select和poll,其实select的底层也是用poll实现的。

      

     1、用户角度

     1 #include <sys/types.h>
     2 #include <sys/stat.h>
     3 #include <stdio.h>
     4 #include <fcntl.h>
     5 #include <sys/time.h>
     6 #include <sys/types.h>
     7 #include <unistd.h>
     8 
     9 
    10 void main()
    11 {
    12     int fd,num;
    13     fd_set rfds;
    14     struct timeval tv;
    15 
    16     fd = open("/dev/chardev",O_RDWR,S_IRUSR | S_IWUSR);
    17     if(fd != -1)
    18     {
    19         while(1)
    20         { 
    21             /*查看chardev是否有输入*/
    22             FD_ZERO(&rfds);  //用来清除一个文件描述符集
    23             FD_SET(fd,&rfds); //将一个文件描述符加入到文件描述符集中
    24             /*设置超时时间为5s*/
    25             tv.tv_sec = 5;
    26             tv.tv_usec = 0;
    27 
    28             //fd应该为所有文件描述符值最大的那个
    29             //这里只有一个所以省略了比较步骤
    30             select(fd+1,&rfds,NULL,NULL,&tv);
    31 
    32             if(FD_ISSET(fd,&rfds)) //判断文件描述符是否被置位了
    33             {
    34                 read(fd,&num,sizeof(int));
    35                 printf("The chardev is %d
    ",num);
    36 
    37 
    38                 if(num == 0)
    39                 {
    40                     close(fd);
    41                     break;
    42                 }
    43             }
    44             else
    45                 printf("NO data within 5 seconds.
    ");
    46         }
    47     }
    48     else
    49         printf("Open file failure.
    ");
    50 }

       代码中使用了select实现多路复用,select的函数的设计比较坑,在我看来就是体验差。不过其原理就是图中所示的那样。用户在调用select是会发生阻塞,而一旦select返回就表示至少有一个文件描述符可以进行读写操作了。但是它不会告诉你具体那个能用,而只是将能用的文件描述符置位。所以我们还得遍历一边所有的文件描述符,看那个被置位了(上面的代码中只有一个文件描述符,所以没用进行遍历)。

    2.内核实现

     1 static int mini210_buttons_read(struct file *filp, char __user *buff,
     2         size_t count, loff_t *offp)
     3 {
     4     unsigned long err;
     5 
     6     if (!ev_press) {
     7         if (filp->f_flags & O_NONBLOCK)
     8             return -EAGAIN;
     9         else
    10             wait_event_interruptible(button_waitq, ev_press);
    11     }
    12 
    13     ev_press = 0;
    14 
    15     err = copy_to_user((void *)buff, (const void *)(&key_values),
    16             min(sizeof(key_values), count));
    17 
    18     return err ? -EFAULT : min(sizeof(key_values), count);
    19 }
    20 
    21 static unsigned int mini210_buttons_poll( struct file *file,
    22         struct poll_table_struct *wait)
    23 {
    24     unsigned int mask = 0;
    25 
    26     poll_wait(file, &button_waitq, wait);
    27     if (ev_press)
    28         mask |= POLLIN | POLLRDNORM;
    29 
    30     return mask;
    31 }

       这里驱动代码只显示一部分,但足够我们分析了。当用户调用select或poll系统调用时,内核会先调用poll_initwait(&table),然后调用驱动程序中mini210_buttons_poll,由此看来select/poll真正的执行者是驱动中的poll函数,同时也说明,如果某个硬件驱动中没有实现该函数,那么即使想使用多路复用技术也是枉然。我们看到poll的实现很简单,就是先等待(poll_wait)一段时间,如果发现数据可用就设置掩码并返回该掩码。这也是为啥用户空间需要不断去检查是否置位的原因。至于poll_wait的具体实现这里就不深究了。

    3.总结

      多路复用技术在服务器程序中使用比较广泛,该技术应该被掌握。

    4.信号驱动IO


      信号驱动IO和异步IO很像,但是由于信号不好控,信号的个数有限(一般操作系统会给用户预留3个左右),所以在实际中使用很少。

    1.用户实现

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <sys/types.h>
     4 #include <sys/stat.h>
     5 #include <fcntl.h>
     6 #include <poll.h>
     7 #include <unistd.h>
     8 #include <signal.h>
     9 
    10 int fd = 0;
    11 /* 信号处理函数当驱动发信号给应用程序时 会来执行 */
    12 void my_signal_fun(int signum)
    13 {
    14    unsigned char key_val;
    15    read(fd, &key_val, 1);
    16    printf("in signal_funkey_val: 0x%x
    ", key_val);
    17 }
    18 
    19 
    20 int main(int argc,char **argv)
    21 {
    22    int Oflags = 0;
    23    if (argc != 1)
    24    {
    25             printf("Usage:%s 
    ",argv[0]);
    26             return 0;
    27    }
    28 
    29 
    30    fd =open("/dev/cmnin",O_RDONLY);
    31    if (fd < 0)
    32    {
    33             printf("open/dev/cmnin error!
    ");
    34             return 0;
    35    }
    36 
    37    signal(SIGIO,my_signal_fun);
    38 
    39    fcntl(fd, F_SETOWN,getpid());  // 告诉内核,发给谁  
    40 
    41    Oflags = fcntl(fd,F_GETFL);   // 获得 flag
    42    fcntl(fd, F_SETFL, Oflags |FASYNC);         //设置flag  异步通知
    43 
    44    while(1)
    45    {
    46             sleep(1000);
    47    }
    48    return 0;
    49 }

       使用信号驱动IO时,首先要将回调函数和一个信号进行绑定,这个信号是操作系统预留给用户的信号。然后向内核注册该信号,告诉内核将来这个信号发给谁,最后将该信号设置为异步通知的方式。到此为之,信号IO的设置完毕,我们的程序就可以继续往下执行干别的事情。一旦内核将我们的数据准备好之后,my_signal_fun会自动被调用去读数据。

    2.内核实现

    static void mini210_cmnins_timer(unsigned long _data)
    {
        struct cmnin_desc *bdata = (struct cmnin_desc *)_data;
        unsigned int tmp;
        //获取端口状态
        tmp = gpio_get_value(bdata->gpio);
        if(tmp){
            key_value = bdata->val & 0x1F;    
        }
        else{
            key_value = bdata->val & 0x0F;
        }
        kill_fasync(&cmninsync_queue, SIGIO, POLL_IN);
    }
    static int mini210_cmnin_read(struct file *filp, char __user *buff,
            size_t count, loff_t *offp)
    {
        unsigned long err;
    
        if (count != 1) {
            return -EAGAIN;
        }
        //将数据copy到用户空间
        err = copy_to_user((void *)buff, (const void *)(&key_value), 1);
        return err ? -EFAULT : 1;
    }
    static int cmnin_fasync(int fd, struct file *filp, int on)
    {
       /* 通过fasync_helper();cmninsync_queue这个结构体使得
         * kill_fasync();能够把信号发送到应用程序的pid
         */
       return fasync_helper(fd,filp, on, &cmninsync_queue);
    }

     mini210_cmnins_timer 会每隔一段时间去查看是否有数据,如果数据就直接放在一个缓冲区中。并且通过 kill_fasync 向用户发信号,当用户收到该信号时会直接实行read操作,这时候 mini210_cmnin_read 就会将缓冲区中的数据copy到用户缓冲区中。

    3.总结

      该技术使用并不多,可以作为了解。

    5.总结


      关于IO这块,不同的操作系统有不同的实现方式。建议大家参考一下以下博文。

      http://blog.csdn.net/historyasamirror/article/details/5778378

      http://www.cnblogs.com/fanzhidongyzby/p/4098546.html

     

  • 相关阅读:
    jquery 表单清空
    CK-Editor content.replace
    CSS DIV HOVER
    返回上一页并刷新与返回上一页不刷新代码
    Google Java编程风格指南中文版
    编程常见英语词汇
    教你如何删除tomcat服务器的stdout.log文件
    @Autowired @Resource @Qualifier的区别
    JSTL标签,EL表达式,OGNL表达式,struts2标签 汇总
    4.11 application未注入报错解决
  • 原文地址:https://www.cnblogs.com/xidongyu/p/5406368.html
Copyright © 2011-2022 走看看