Linux设备驱动中的阻塞和非阻塞I/O

阻塞和非阻塞I/O

阻塞操作是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作。被挂起的进程进入睡眠状态,被从调度器的运行队列移走,直到等待的条件被满足。而非阻塞操作的进程在不能进行设备操作时,并不挂起,它要么放弃,要么不停地查询,直至可以进行操作为止。

驱动程序通常需要提供这样的能力:当应用程序进行read()、write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应在设备驱动的xxx_read()、xxx_write()等操作中将进程阻塞直到资源可以获取,此后,应用程序的read()、write()等调用才返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read()、xxx_write()等操作应立即返回,read()、write()等系统调用也随即被返回,应用程序收到-EAGAIN返回值。

下图中,在阻塞访问时,不能获取资源的进程将进入休眠,它将CPU资源“礼让”给其他进程。因为阻塞的进程会进入休眠状态,所以必须确保有一个地方能够唤醒休眠的进程,否则,进程就真的“寿终就寝”了。唤醒进程的地方最大可能发生在中断里面,因为在硬件资源获得的同时往往伴随着一个中断。而非阻塞的进程则不断尝试,直到可以进行I/O。

以下代码分别演示了以阻塞和非阻塞方式读取串口一个字符的代码。前者在打开文件的时候没有O_NONBLOCK标记,后者使用O_NONBLOCK标记打开文件。

  1. char buf;
  2. fd = open("/dev/ttyS1", O_RDWR);
  3. /* code */
  4. res = read(fd, &buf, 1);
  5. if (res == 1)
  6.     printf("%c\n", buf);
  1. char buf;
  2. fd = open("/dev/ttyS1", O_RDWR | O_NONBLOCK);
  3. /* code */
  4. while (read(fd, &buf, 1) != 1)
  5.     continue;
  6.  
  7. printf("%c\n", buf);

除了在打开文件时可以指定阻塞还是非阻塞方式以外,在文件打开后,也可以通过ioctl()和fcntl()改变读写的方式,如从阻塞变更为非阻塞或者从非阻塞变更为阻塞。例如,调用fcntl(fd, FSETFL, O_NONBLOCK)可以设置fd对应的I/O为非阻塞。

等待队列

在Linux驱动程序中,可以使用等待队列(Wait Queue)来实现阻塞进程的唤醒。等待队列很早就作为一个基本的功能单位出现在Linux内核里了,它以队列为基础数据结构,与进程调度机制紧密结合,可以用来同步对系统资源的访问,信号量在内核中也依赖等待队列来实现。

Linux内核提供了如下关于等待队列的操作。

1.定义“等待队列头部”

  • wait_queue_head_t my_queue;

wait_queue_head_t是wait_queue_head结构体的一个typedef。

2.初始化“等待队列头部”

  • init_waitqueue_head(&my_queue);

而下面的DECLARE_WAIT_QUEUE_HEAD()宏可以作为定义并初始化等待队列头部的“快捷方式”。

  • DECLARE_WAIT_QUEUE_HEAD(name)

3.定义等待队列元素

  • DECLARE_WAITQUEUE(name, tsk)

该宏用于定义并初始化一个名为name的等待队列元素。

4.添加/移除等待队列

  • void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);
  • void remove_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);

add_wait_queue()用于将等待队列元素wait添加到等待队列头部q指向的双向链表中,而remove_wait_queue()用于将等待队列元素wait从由q头部指向的链表中移除。

5.等待事件

  • #define wait_event(wq_head, condition)
  • #define wait_event_interruptible(wq_head, condition)
  • #define wait_event_timeout(wq_head, condition, timeout)
  • #define wait_event_interruptible_timeout(wq_head, condition, timeout)

等待第一个参数queue作为等待队列头部的队列被唤醒,而且第2个参数condition必须满足,否则继续阻塞。wait_event()和wait_event_interruptible()的区别在于后者可以被信号打断,而前者不行。加上_timeout后的宏意味着阻塞等待的超时时间,以jiffy为单位,在第3个参数的timeout到达时,不论condition是否满足,均返回。

6.唤醒队列

  • wake_up(x)
  • wake_up_interruptible(x)

上述操作会唤醒以queue作为等待队列头部的队列中所有的进程。

wake_up()应该与wait_event()或wait_event_timeout()成对使用,而wake_up_interruptible()则应与wait_event_interruptible()或wait_event_interruptible_timeout()成对使用。wake_up()可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE的进程,而wake_up_interruptible()只能唤醒处于TASK_INTERRUPTIBLE的进程。

7.在等待队列上睡眠

在linux5版本中找不到sleep_on函数,暂留。


以下代码演示了一个在设备驱动中使用等待队列的模板,在进行写I/O操作的时候,判断设备是否可写,如果不可写且为阻塞I/O,则进程睡眠并挂起到等待队列。

  1. ssize_t xxx_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos)
  2. {
  3.     /* code */
  4.     DECLARE_WAITQUEUE(wait, current); /* 定义等待队列元素 */
  5.     add_wait_queue(&xxx_wait, &wait); /* 添加元素到等到队列 */
  6.  
  7.     /* 等待设备缓冲区可写 */
  8.     do
  9.     {
  10.         avail = device_writeable();
  11.         if (avail < 0)
  12.         {
  13.             if (filp->f_flags & O_NONBLOCK) /* 非阻塞 */
  14.             {
  15.                 ret = -EAGAIN;
  16.                 goto out;
  17.             }
  18.             __set_current_state(TASK_INTERRUPTIBLE); /* 改变进程状态 */
  19.             schedule(); /* 调度其他进程执行 */
  20.             if (signal_pending(current) ) /* 如果是因为信号唤醒 */
  21.             {
  22.                 ret = -ERESTARTSYS;
  23.                 goto out;
  24.             }
  25.         }
  26.     } while (avail < 0);
  27.    
  28.     /* 写设备缓冲区 */
  29.     device_write();
  30.  
  31.     out:
  32.     remove_wait_queue(&xxx_wait, &wait); /* 将元素移出xxx_wait指引的队列 */
  33.     set_current_state(TASK_RUNNING);    /* 设置进程状态TASK_RUNNING */
  34.     return ret;
  35. }

读懂以上代码对理解Linux进程状态切换非常重要,所以提请反复阅读此段代码,直至完全领悟,几个要点如下。

1)如果是非阻塞访问(O_NONBLOCK被设置),设备忙时,直接返回-EAGAIN。

2)对于阻塞访问,会调用__set_current_state(TASK_INTERRUPTIBLE)进行进程状态切换并显式通过schedule()调度其他进程执行。

3)醒来的时候要注意,由于调度出去的时候,进程状态是TASK_INTERRUPTIBLE,即浅度睡眠,所以唤醒它的有可能是信号,因此,我们首先通过signal_pending(current)了解是不是信号唤醒的,如果是,立即返回-ERESTARTSYS。

DECLARE_WAITQUEUE、add_wait_queue这两个动作加起来完成的效果如下图所示。在wait_queue_head_t指向的链表上,新定义的wait_queue元素被插入,而这个新插入的元素绑定了一个task_struct(当前做xxx_write的current,这也是DECLARE_WAITQUEUE使用current作为参数的原因)。

支持阻塞操作的globalfifo设备驱动

现在我们给globalmem增加这样的约束:把globalmem中的全局内存变成一个FIFO,只有当FIFO中有数据的时候(即有进程把数据写到这个FIFO而且没有被读进程读空),读进程才能把数据读出,而且读取后的数据会从globalmem的全局内存中被拿掉;只有当FIFO不是满的时(即还有一些空间未被写,或写满后被读进程从这个FIFO中读出了数据),写进程才能往这个FIFO中写入数据。

现在,将globalmem重命名为globalfifo,在globalfifo中,读FIFO将唤醒FIFO的进程(如果之前FIFO正好是满的),而写进程也将唤醒读FIFO的进程(如果之前FIFO正好是空的)。首先,需要修改设备结构体,在其中增加两个等待队列头部,分别对应于读和写,如下代码所示。

  1. struct globalfifo_dev
  2. {
  3.     struct cdev cdev;
  4.     unsigned int current_len;
  5.     unsigned char mem[GLOBALFIFO_SIZE];
  6.     struct mutex mutex;
  7.     wait_queue_head_t r_wait;
  8.     wait_queue_head_t w_wait;
  9. };

与globalfifo设备结构体的另一个不同是增加了current_len成员以用于表征目前FIFO中有效数据的长度。current_len等于0意味着FIFO空,current_len等于GLOBALFIFO_SIZE意味着FIFO满。

这两个等待队列头部需在设备模块加载函数中调用init_waitqueue_head()被初始化,新的设备驱动模块加载函数如下代码所示。

  1. static int __init globalfifo_init(void)
  2. {
  3.     int ret;
  4.     dev_t devno = MKDEV(globalfifo_major, 0);
  5.  
  6.     if (globalfifo_major)
  7.     {
  8.         ret = register_chrdev_region(devno, 1, "globalfifo");
  9.     }
  10.     else
  11.     {
  12.         ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
  13.         globalfifo_major = MAJOR(devno);
  14.     }
  15.  
  16.     if (ret < 0)
  17.     {
  18.         return ret;
  19.     }
  20.  
  21.     globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
  22.     if (!globalfifo_devp)
  23.     {
  24.         ret = -ENOMEM;
  25.         goto fail_malloc;
  26.     }
  27.  
  28.     globalfifo_setup_cdev(globalfifo_devp, 0);
  29.  
  30.     mutex_init(&globalfifo_devp->mutex);
  31.  
  32.     init_waitqueue_head(&globalfifo_devp->r_wait);
  33.     init_waitqueue_head(&globalfifo_devp->w_wait);
  34.  
  35.     return 0;
  36.  
  37. fail_malloc:
  38.     unregister_chrdev_region(devno, 1);
  39.     return ret;
  40. }
  41. module_init(globalfifo_init);

设备驱动读写操作需要被修改,在读函数中需要增加唤醒w_wait等待队列的语句,而在写操作中唤醒r_wait等待队列的语句,如下代码所示。

  1. static ssize_t globalfifo_read(struct file* filp, char __user* buf, size_t count, loff_t* ppos)
  2. {
  3.     int ret;
  4.     struct globalfifo_dev* dev = filp->private_data;
  5.     DECLARE_WAITQUEUE(wait, current);
  6.  
  7.     mutex_lock(&dev->mutex);
  8.     add_wait_queue(&dev->r_wait, &wait);
  9.  
  10.     while (dev->current_len == 0)
  11.     {
  12.         if (filp->f_flags & O_NONBLOCK)
  13.         {
  14.             ret = -EAGAIN;
  15.             goto out;
  16.         }
  17.  
  18.         __set_current_state(TASK_INTERRUPTIBLE);
  19.         mutex_unlock(&dev->mutex);
  20.  
  21.         schedule();
  22.         if (signal_pending(current) )
  23.         {
  24.             ret = -ERESTARTSYS;
  25.             goto out2;
  26.         }
  27.  
  28.         mutex_lock(&dev->mutex);
  29.     }
  30.  
  31.     if (count > dev->current_len)
  32.     {
  33.         count = dev->current_len;
  34.     }
  35.  
  36.     if (copy_to_user(buf, dev->mem, count) )
  37.     {
  38.         ret = -EFAULT;
  39.         goto out;
  40.     }
  41.     else
  42.     {
  43.         memcpy(dev->mem, dev->mem + count, dev->current_len - count);
  44.         dev->current_len -= count;
  45.         printk(KERN_INFO "read %d bytes(s), current len = %d\n", count, dev->current_len);
  46.        
  47.         wake_up_interruptible(&dev->w_wait);
  48.  
  49.         ret = count;
  50.     }
  51.    
  52. out:
  53.     mutex_unlock(&dev->mutex);
  54. out2:
  55.     remove_wait_queue(&dev->w_wait, &wait);
  56.     set_current_state(TASK_RUNNING);
  57.     return ret;
  58. }
  59.  
  60. ssize_t globalfifo_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos)
  61. {
  62.     struct globalfifo_dev* dev = filp->private_data;
  63.     int ret;
  64.     DECLARE_WAITQUEUE(wait, current);
  65.    
  66.     mutex_lock(&dev->mutex);
  67.     add_wait_queue(&dev->w_wait, &wait);
  68.  
  69.     while (dev->current_len == GLOBALFIFO_SIZE)
  70.     {
  71.         if (filp->f_flags & O_NONBLOCK)
  72.         {
  73.             ret = -EAGAIN;
  74.             goto out;
  75.         }
  76.  
  77.         __set_current_state(TASK_INTERRUPTIBLE);
  78.         mutex_unlock(&dev->mutex);
  79.  
  80.         schedule();
  81.         if (signal_pending(current))
  82.         {
  83.             ret = -ERESTARTSYS;
  84.             goto out2;
  85.         }
  86.  
  87.         mutex_lock(&dev->mutex);
  88.     }
  89.  
  90.     if (count > GLOBALFIFO_SIZE - dev->current_len)
  91.     {
  92.         count = GLOBALFIFO_SIZE - dev->current_len;
  93.     }
  94.  
  95.     if (copy_from_user(dev->mem + dev->current_len, buf, count) )
  96.     {
  97.         ret = -EFAULT;
  98.         goto out;
  99.     }
  100.     else
  101.     {
  102.         dev->current_len += count;
  103.         printk(KERN_INFO "written %d byte(s), current len = %d\n", count, dev->current_len);
  104.  
  105.         wake_up_interruptible(&dev->r_wait);
  106.        
  107.         ret = count;
  108.     }
  109.    
  110. out:
  111.     mutex_unlock(&dev->mutex);
  112. out2:
  113.     remove_wait_queue(&dev->w_wait, &wait);
  114.     set_current_state(TASK_RUNNING);
  115.     return ret;
  116. }

globalfifo_read()通过第5行和第8行将自己加到了r_wait这个队列里面,但此时读的进程并未睡眠,之后第18行调用__set_current_state(TASK_INTERRUPTIBLE)时,也只是标记了task_struct的一个浅度睡眠标记,并未真正睡眠,直到21行调用schedule(),读进程进入睡眠。进行完读操作后,第47行调用wake_up_interruptible(&dev->w_wait)唤醒可能阻塞的写进程。globalfifo_write()的过程与之类似。

关注代码的19行和78行,无论是读函数还是写函数,进入schedule()把自己切换出去之前,都主动释放了互斥体。原因是如果读进程阻塞,实际意味着FIFO空,必须依赖写的进程往FIFO里面写东西来唤醒它,但是写的进程为了写FIFO,它必须拿到这个互斥体来访问FIFO这个临界资源,如果读进程把自己调度出去之前不释放这个互斥体,那么写进程之间就死锁了。所谓死锁,就是多个进程循环等待他方占有的资源而无期限地僵持下去。如果没有外力的作用,那么死锁涉及的各个进程将永远处于封锁状态。因此驱动工程师一定要注意:当多个等待队列、信号量、互斥体等机制同时出现时,谨慎死锁!

现在回过来了看一下代码的第12行和71行,发现在设备驱动的read()、write()等功能函数中,可以通过filp->f_flags标志获得用户空间是否要求非阻塞访问。驱动中可以依据此标志判断用户究竟要求阻塞还是非阻塞访问,从而进行不同的处理。

代码中还有一个关键点,就是无论读函数还是写函数,在进行真正的读写之前,都要再次判断设备是否可以读写,见第10行和第69行。主要目的是为了让并发的读或者写都正确。设想如果两个读进程都阻塞在读上,写进程执行的wake_up_interruptible实际会同时唤醒它们,其中先执行的那个进程可能会率先将FIFO再次读空!

在用户空间验证globalfifo的读写

与之前的操作一样:

  • $ sudo insmod main.ko
  • $ cat /proc/devices
  • 231 globalmem
  • $ sudo mknod -m 0666 /dev/globalmem c 231 0

然后启用两个控制台,都执行cat命令;再启用一个进程执行echo命令。每当echo进程写入一串数据,cat进程就立即将该串数据显示出来。

其他记录

1. 记录完整代码如下。

  1. #include <linux/module.h>
  2. #include <linux/types.h>
  3. #include <linux/sched.h>
  4. #include <linux/init.h>
  5. #include <linux/cdev.h>
  6. #include <linux/slab.h>
  7. #include <linux/poll.h>
  8. #include <linux/interrupt.h>
  9. #include <linux/sched/signal.h>
  10.  
  11. #define FIFO_CLEAR 0x01
  12.  
  13. #define GLOBALFIFO_SIZE 0x1000
  14.  
  15. #define GLOBALFIFO_MAJOR 231
  16. static int globalfifo_major = GLOBALFIFO_MAJOR;
  17. module_param(globalfifo_major, int, S_IRUGO);
  18.  
  19. struct globalfifo_dev
  20. {
  21.     struct cdev cdev;
  22.     unsigned int current_len;
  23.     unsigned char mem[GLOBALFIFO_SIZE];
  24.     struct mutex mutex;
  25.     wait_queue_head_t r_wait;
  26.     wait_queue_head_t w_wait;
  27. };
  28.  
  29. struct globalfifo_dev* globalfifo_devp;
  30.  
  31. static int globalfifo_open(struct inode* inode, struct file* filp)
  32. {
  33.     filp->private_data = globalfifo_devp;
  34.     return 0;
  35. }
  36.  
  37. static int globalfifo_release(struct inode* inode, struct file* filp)
  38. {
  39.     return 0;
  40. }
  41.  
  42. static long globalfifo_ioctl(struct file* filp, unsigned int cmd, unsigned long arg)
  43. {
  44.     struct globalfifo_dev* dev = filp->private_data;
  45.  
  46.     switch (cmd)
  47.     {
  48.     case FIFO_CLEAR:
  49.         mutex_lock(&dev->mutex);
  50.         dev->current_len = 0;
  51.         memset(dev->mem, 0, GLOBALFIFO_SIZE);
  52.         mutex_unlock(&dev->mutex);
  53.  
  54.         printk(KERN_INFO "globalfifo is set to zero\n");
  55.         break;
  56.    
  57.     default:
  58.         return -EINVAL;
  59.     }
  60.  
  61.     return 0;
  62. }
  63.  
  64. static ssize_t globalfifo_read(struct file* filp, char __user* buf, size_t count, loff_t* ppos)
  65. {
  66.     int ret;
  67.     struct globalfifo_dev* dev = filp->private_data;
  68.     DECLARE_WAITQUEUE(wait, current);
  69.  
  70.     mutex_lock(&dev->mutex);
  71.     add_wait_queue(&dev->r_wait, &wait);
  72.  
  73.     while (dev->current_len == 0)
  74.     {
  75.         if (filp->f_flags & O_NONBLOCK)
  76.         {
  77.             ret = -EAGAIN;
  78.             goto out;
  79.         }
  80.  
  81.         __set_current_state(TASK_INTERRUPTIBLE);
  82.         mutex_unlock(&dev->mutex);
  83.  
  84.         schedule();
  85.         if (signal_pending(current) )
  86.         {
  87.             ret = -ERESTARTSYS;
  88.             goto out2;
  89.         }
  90.  
  91.         mutex_lock(&dev->mutex);
  92.     }
  93.  
  94.     if (count > dev->current_len)
  95.     {
  96.         count = dev->current_len;
  97.     }
  98.  
  99.     if (copy_to_user(buf, dev->mem, count) )
  100.     {
  101.         ret = -EFAULT;
  102.         goto out;
  103.     }
  104.     else
  105.     {
  106.         memcpy(dev->mem, dev->mem + count, dev->current_len - count);
  107.         dev->current_len -= count;
  108.         printk(KERN_INFO "read %d bytes(s), current len = %d\n", count, dev->current_len);
  109.        
  110.         wake_up_interruptible(&dev->w_wait);
  111.  
  112.         ret = count;
  113.     }
  114.    
  115. out:
  116.     mutex_unlock(&dev->mutex);
  117. out2:
  118.     remove_wait_queue(&dev->w_wait, &wait);
  119.     set_current_state(TASK_RUNNING);
  120.     return ret;
  121. }
  122.  
  123. ssize_t globalfifo_write(struct file* filp, const char __user* buf, size_t count, loff_t* ppos)
  124. {
  125.     struct globalfifo_dev* dev = filp->private_data;
  126.     int ret;
  127.     DECLARE_WAITQUEUE(wait, current);
  128.    
  129.     mutex_lock(&dev->mutex);
  130.     add_wait_queue(&dev->w_wait, &wait);
  131.  
  132.     while (dev->current_len == GLOBALFIFO_SIZE)
  133.     {
  134.         if (filp->f_flags & O_NONBLOCK)
  135.         {
  136.             ret = -EAGAIN;
  137.             goto out;
  138.         }
  139.  
  140.         __set_current_state(TASK_INTERRUPTIBLE);
  141.         mutex_unlock(&dev->mutex);
  142.  
  143.         schedule();
  144.         if (signal_pending(current))
  145.         {
  146.             ret = -ERESTARTSYS;
  147.             goto out2;
  148.         }
  149.  
  150.         mutex_lock(&dev->mutex);
  151.     }
  152.  
  153.     if (count > GLOBALFIFO_SIZE - dev->current_len)
  154.     {
  155.         count = GLOBALFIFO_SIZE - dev->current_len;
  156.     }
  157.  
  158.     if (copy_from_user(dev->mem + dev->current_len, buf, count) )
  159.     {
  160.         ret = -EFAULT;
  161.         goto out;
  162.     }
  163.     else
  164.     {
  165.         dev->current_len += count;
  166.         printk(KERN_INFO "written %d byte(s), current len = %d\n", count, dev->current_len);
  167.  
  168.         wake_up_interruptible(&dev->r_wait);
  169.        
  170.         ret = count;
  171.     }
  172.    
  173. out:
  174.     mutex_unlock(&dev->mutex);
  175. out2:
  176.     remove_wait_queue(&dev->w_wait, &wait);
  177.     set_current_state(TASK_RUNNING);
  178.     return ret;
  179. }
  180.  
  181. static const struct file_operations globalfifo_fops =
  182. {
  183.     .owner = THIS_MODULE,
  184.     .read = globalfifo_read,
  185.     .write = globalfifo_write,
  186.     .unlocked_ioctl = globalfifo_ioctl,
  187.     .open = globalfifo_open,
  188.     .release = globalfifo_release,
  189. };
  190.  
  191. static void globalfifo_setup_cdev(struct globalfifo_dev* dev, int index)
  192. {
  193.     int err, devno = MKDEV(globalfifo_major, index);
  194.  
  195.     cdev_init(&dev->cdev, &globalfifo_fops);
  196.     dev->cdev.owner = THIS_MODULE;
  197.     err = cdev_add(&dev->cdev, devno, 1);
  198.     if (err)
  199.     {
  200.         printk(KERN_NOTICE "Error %d : adding globalfifo%d", err, index);
  201.     }
  202. }
  203.  
  204. static int __init globalfifo_init(void)
  205. {
  206.     int ret;
  207.     dev_t devno = MKDEV(globalfifo_major, 0);
  208.  
  209.     if (globalfifo_major)
  210.     {
  211.         ret = register_chrdev_region(devno, 1, "globalfifo");
  212.     }
  213.     else
  214.     {
  215.         ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
  216.         globalfifo_major = MAJOR(devno);
  217.     }
  218.  
  219.     if (ret < 0)
  220.     {
  221.         return ret;
  222.     }
  223.  
  224.     globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
  225.     if (!globalfifo_devp)
  226.     {
  227.         ret = -ENOMEM;
  228.         goto fail_malloc;
  229.     }
  230.  
  231.     globalfifo_setup_cdev(globalfifo_devp, 0);
  232.  
  233.     mutex_init(&globalfifo_devp->mutex);
  234.  
  235.     init_waitqueue_head(&globalfifo_devp->r_wait);
  236.     init_waitqueue_head(&globalfifo_devp->w_wait);
  237.  
  238.     return 0;
  239.  
  240. fail_malloc:
  241.     unregister_chrdev_region(devno, 1);
  242.     return ret;
  243. }
  244. module_init(globalfifo_init);
  245.  
  246. static void __exit globalfifo_exit(void)
  247. {
  248.     cdev_del(&globalfifo_devp->cdev);
  249.     kfree(globalfifo_devp);
  250.     unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
  251. }
  252. module_exit(globalfifo_exit);

2. rmmod ko文件可以移除指定模块。


3. Visual Studio Code中按Ctrl+Shift+P键可以选择编辑文件指定头文件。以下记录当前代码指定的头文件。

  1. "/usr/src/linux-headers-5.3.0-53/include/",
  2. "/usr/src/linux-headers-5.3.0-53/arch/x86/include/",
  3. "/usr/src/linux-headers-5.3.0-53/include/uapi/",
  4. "/usr/src/linux-headers-5.3.0-53/arch/ia64/include/",
  5. "/usr/src/linux-headers-5.3.0-53-generic/include/",
  6. "/usr/src/linux-headers-5.3.0-53-generic/arch/x86/include/generated/"

总结和思考

通过这个例子了解了如何设置进程状态,阻塞的实现是使进程处于睡眠状态。同时也了解了fifo的锁实现,之前了解到linux下有fifo管道文件,可能实现原理有相通之处。

同时,这章也留下一处疑惑。同时开两个进程cat,往设备文件里写内容时,总是第一个读进程获得内容,希望之后学习能了解为什么这样(感觉可能和等待队列有关)。