• 【Linux开发】linux设备驱动归纳总结(三):5.阻塞型IO实现


    linux设备驱动归纳总结(三):5.阻塞型IO实现


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    一、休眠简介:


    进程休眠,简单的说就是正在运行的进程让出CPU。休眠的进程会被内核搁置在在一边,只有当内核再次把休眠的进程唤醒,进程才会会重新在CPU运行。这是内核中的进程调度,以后的章节会介绍。

    现在应该先知道这样的一个概念,一个CPU在同一时间只能有一个进程在运行,在宏观上,我们觉得是所有进程同时进行的。实际上并不是这样,内核给每个进程分配了4G的虚拟内存,并且让每个进程傻乎乎的以为自己霸占着CPU运行。同时,内核暗中的将所有的进程按一定的算法将CPU轮流的给每个进程使用,而休眠就是进程没有被运行时的一种形式。在休眠下,进程不占用CPU,等待被唤醒。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    二、阻塞型IO的实现:


    知道什么是休眠,接下来就好办了。接下来就是要实现阻塞型的readwrite函数,函数将实现一下功能:

    read:当没数据可读时,函数让出CPU,进入休眠状态,等待write写入数据后唤醒read

    write:写入数据,并唤醒read


    先上函数:我只上需要修改的函数,openrelease就不贴了

    /*3rd_char_5/1st/test.c*/

    1 #include

    2 #include

    3 #include

    4 #include

    5

    6 #include

    7 #include

    8

    9 #include

    10 #include

    11

    12 #define DEBUG_SWITCH 1

    13 #if DEBUG_SWITCH

    14 #define P_DEBUG(fmt, args...) printk("<1>" "[%s]"fmt, __FUNCT ION__, ##args)

    15 #else

    16 #define P_DEBUG(fmt, args...) printk("<7>" "[%s]"fmt, __FUNCT ION__, ##args)

    17 #endif

    18

    19 #define DEV_SIZE 100

    20

    21 struct _test_t{

    22 char kbuf[DEV_SIZE];

    23 unsigned int major;

    24 unsigned int minor;

    25 unsigned int cur_size;

    26 dev_t devno;

    27 struct cdev test_cdev;

    28 wait_queue_head_t test_queue; //1、定义等待队列头

    29 };

    30

    。。。。。。省略。。。。。。。

    43

    44 ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)

    45 {

    46 int ret;

    47 struct _test_t *dev = filp->private_data;

    48

    49 /*休眠*/

    50 P_DEBUG("read data..... ");

    51 if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0))

    52 return - ERESTARTSYS;

    53

    54 if (copy_to_user(buf, dev->kbuf, count)){

    55 ret = - EFAULT;

    56 }else{

    57 ret = count;

    58 dev->cur_size -= count;

    59 P_DEBUG("read %d bytes, cur_size:[%d] ", count, dev->cur_size);

    60 }

    61

    62 return ret; //返回实际写入的字节数或错误号

    63 }

    64

    65 ssize_t test_write(struct file *filp, const char __user *buf, size_t count, loff_t *offset)

    66 {

    67 int ret;

    68 struct _test_t *dev = filp->private_data;

    69

    70 if(copy_from_user(dev->kbuf, buf, count)){

    71 ret = - EFAULT;

    72 }else{

    73 ret = count;

    74 dev->cur_size += count;

    75 P_DEBUG("write %d bytes, cur_size:[%d] ", count, dev->cur_size);

    76 P_DEBUG("kbuf is [%s] ", dev->kbuf);

    77 /*唤醒*/

    78 wake_up_interruptible(&dev->test_queue);

    79 }

    80

    81 return ret; //返回实际写入的字节数或错误号

    82 }

    83

    84 struct file_operations test_fops = {

    85 .open = test_open,

    86 .release = test_close,

    87 .write = test_write,

    88 .read = test_read,

    89 };

    90

    91 struct _test_t my_dev;

    92

    93 static int __init test_init(void) //模块初始化函数

    94 {

    95 int result = 0;

    96 my_dev.cur_size = 0;

    97 my_dev.major = 0;

    98 my_dev.minor = 0;

    99

    100 if(my_dev.major){

    101 my_dev.devno = MKDEV(my_dev.major, my_dev.minor);

    102 result = register_chrdev_region(my_dev.devno, 1, "test new driver") ;

    103 }else{

    104 result = alloc_chrdev_region(&my_dev.devno, my_dev.minor, 1, "test alloc diver");

    105 my_dev.major = MAJOR(my_dev.devno);

    106 my_dev.minor = MINOR(my_dev.devno);

    107 }

    108

    109 if(result < 0){

    110 P_DEBUG("register devno errno! ");

    111 goto err0;

    112 }

    113

    114 printk("major[%d] minor[%d] ", my_dev.major, my_dev.minor);

    115

    116 cdev_init(&my_dev.test_cdev, &test_fops);

    117 my_dev.test_cdev.owner = THIS_MODULE;

    118 /*初始化等待队列头,注意函数调用的位置*/

    119 init_waitqueue_head(&my_dev.test_queue);

    120

    121 result = cdev_add(&my_dev.test_cdev, my_dev.devno, 1);

    122 if(result < 0){

    123 P_DEBUG("cdev_add errno! ");

    124 goto err1;

    125 }

    126

    127 printk("hello kernel ");

    128 return 0;

    129

    130 err1:

    131 unregister_chrdev_region(my_dev.devno, 1);

    132 err0:

    133 return result;

    134 }

    为了方便讲解,函数我精简了很多,红色好亮代码是新加的知识点,其他都是之前已经讲过的。


    下面开始介绍上面使用的知识:

    知识1)什么是等待队列。

    前面说了进程休眠,而其他进程为了能够唤醒休眠的进程,它必须知道休眠的进程在哪里,出于这样的原因,需要有一个称为等待队列的结构体。等待队列是一个存放着等待某个特定事件进程链表

    在这里的程序,用于存放等待唤醒的进程。


    既然是队列,当然要有个队列头,在使用等待队列之前,必须先定义并初始化等待队列头。

    先看一下队列头的样子:

    /*linux/wait.h*/

    50 struct __wait_queue_head {

    51 spinlock_t lock; //这个是自旋锁,在这里不需要理会。

    52 struct list_head task_list; //这就是队列头中的核心,链表头。

    53 };

    54 typedef struct __wait_queue_head wait_queue_head_t;

    说白了就是定义并初始化一个链表。以后就能够在这个链表添加需要等待的进程了。


    定义并初始化队列头有两种方法:

    1)静态定义并初始化,一个函数执行完两个操作。省力又省心。

    DECLARE_WAIT_QUEUE_HEAD(name)

    使用:定义并初始化一个叫name的等待队列。

    2)分开两步执行。

    2.1)定义

    wait_queue_head_t test_queue;

    2.2)初始化

    init_waitqueue_head(&test_queue);


    我使用的是第二种方法,这些都是在加载模块时应该完成的操作。其中,等待队列头的定义我放在”struct _test_t”结构体中,初始化放在模块加载函数中。

    这里值得注意的是初始化函数的位置,它必须在cdev添加函数”cdev_add”。因为”cdev_add”执行成功就意味着设备可以被操作,设备被操作前当然需要把所有的事情都干完,包括等待队列的初始化。


    知识2)进程休眠


    test_read函数中就实现了进程休眠,使用了函数”wait_evenr_interruptible”

    wait_event_interruptible(wq, condition)

    使用:

    如果condition为真,函数将进程添加到等待队列头wq并等待唤醒。

    返回值:

    添加成功返回0。另外,interruptition的意思是休眠进程可以被某个信号中断中断,如果被中断,驱动程序应该返回-ERESTARTSYS


    这有一类的函数,操作跟”wait_evevt_interruptition”类似

    wait_event(queue, condition)

    /*函数成功会进入不可中断休眠,不推荐*/
    wait_event_interruptible(queue, condition)

    /*函数调用成功会进入可中断休眠,推荐,返回非零值意味着休眠被中断,且驱动应返回-ERESTARTSYS*/
    wait_event_timeout(queue, condition, timeout)
    wait_event_interruptible_timeout(queue, condition, timeout)
    /*
    比上面两个函数多了限时功能,若休眠超时,则不管条件为何值返回0*/


    上面的四个函数大致都是完成一下的操作:

    wait_event_interruptible(dev->test_queue, dev->cur_size > 0)举例:

    1、定义并初始化一个wait_queue_t结构体,然后将它添加到等待队列test_queue中。

    2、更改进程的状态,休眠的状态有两种:(可中断休眠)TASK_INTERRUPTIBLE和(不可中断休眠)TASK_UNINTERRUPTIBLE。上面的函数会切换到可中断休眠。

    3、判断条件 dev->cur_size > 0是否成立,如果不成立,则调用函数schedule()让出CPU。注意,一旦让出CPU进入休眠后,进程再次被唤醒后就会从这一步开始,再次检测条件是否成立,如果还是不成立,继续让出CPU,等待下一次的唤醒。如果成立,则进行下一步的操作。所以,这个函数的条件会被多次判断,因此这个判断语句并不能对这个进程带来任何副作用。

    4、条件成立后做一些相应的清理工作,并把进程状态更改为TASK_RUNNING


    我刚学的时候还在纳闷,为什么定义了一个队列头后,就可以在test_read函数直接根据条件进入休眠?

    现在我总算是明白了。进程休眠是需要在等待队列添加一个wait_queue_t结构体,但是上面的休眠函数内部已经帮我们实现了这个操作。


    既然上面的函数有四个操作,内核肯定会有拆分出来的操作。这就是《linux设备驱动程序》(第三版)P155上面讲的高级休眠。有兴趣可以自己看书。


    知识3)唤醒休眠进程。

    test_write函数中使用wake_up_interruptible(&dev->test_queue)唤醒指定等待队列中睡眠的进程。

    这里也有两个类似的函数:

    void wake_up(wait_queue_head_t *queue); //唤醒等待队列中所有休眠的进程
    void wake_up_interruptible(wait_queue_head_t *queue); //
    唤醒等待队列中所有可中断睡眠的进程

    一般来说,用 wake_up 唤醒 wait_event ;用 wake_up_interruptible 唤醒wait_event_interruptible


    一旦上面的函数调用成功,等待队列里面所有符合休眠状态的进程都会被唤醒,所有进程都会执行上面说的休眠函数的第三步——轮流占用CPU来是判断时候否符合条件。一旦有一个进程符合条件,那个进程就会运行下去,其他进程变回原来的休眠状态等待下一次的被唤醒。如果全部都不符合,全部都会变回原来的休眠状态。

    linux设备驱动程序》P160有介绍独占等待的概念,大概的意思就是不要让所有符号休眠状态的进程同时被唤醒,只唤醒其中的一个。


    知识点已经介绍完,总结一下上面驱动函数的操作:

    1)首先需要定义并初始化一个等待队列。

    2test_read函数中,如果条件不符合,调用该函数的进程就会进入休眠。

    3)每当另一个进程调用test_write函数唤醒等待队列,test_read中的函数就会再一次判断条件是否符合,如果不符合,就会继续休眠,直到哪次的唤醒时条件符合。


    写两个应用程序验证驱动:

    /*app_read.c*/

    1 #include

    2 #include

    3 #include

    4 #include

    5

    6 int main(void)

    7 {

    8 char buf[20];

    9 int fd;

    10 int ret;

    11

    12 fd = open("/dev/test", O_RDWR);

    13 if(fd < 0)

    14 {

    15 perror("open");

    16 return -1;

    17 }

    18

    19 read(fd, buf, 10);

    20 printf("buf is [%s] ", buf);

    21

    22 close(fd);

    23 return 0;

    24 }


    /*app_write*/

    1 #include

    2 #include

    3 #include

    4 #include

    5

    6 int main(void)

    7 {

    8 char buf[20];

    9 int fd;

    10 int ret;

    11

    12 fd = open("/dev/test", O_RDWR);

    13 if(fd < 0)

    14 {

    15 perror("open");

    16 return -1;

    17 }

    18

    19 write(fd, "xiao bai", 10);

    20

    21 close(fd);

    22 return 0;

    23 }


    验证一下:

    [root: 1st]# insmod test.ko

    major[253] minor[0]

    hello kernel

    [root: 1st]# mknod /dev/test c 253 0

    [root: 1st]# ./app_read& //先后台运行app_read

    [test_read]read data..... //因为没有数据,程序阻塞

    [root: 1st]# ./app_write //再运行app_write

    [test_write]write 10 bytes, cur_size:[10]

    [test_write]kbuf is [xiao bai]

    [test_read]read 10 bytes, cur_size:[0] //read继续执行

    buf is [xiao bai] //打印读到的内容

    [1] + Done ./app_read

    [root: 1st]#


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    三、非阻塞型操作的实现


    上面的程序虽然不是很完善,但基本的功能已经实现了,但还有一个问题需要解决,当我们在应用层以非阻塞方式打开文件时,读写操作不满足条件时并不阻塞,而是直接返回。


    实现非阻塞操作也很简单,判断filp->f_flags中的是否存在O_NONBLOCK标志(标志在定义,并被自动包含),如果有就返回-EAGAIN


    贴上修改后的程序,其实就加了两行:

    /*3rd_char_5/2nd/test.c*/

    44 ssize_t test_read(struct file *filp, char __user *buf, size_t count, loff_t *offset)

    45 {

    46 int ret;

    47 struct _test_t *dev = filp->private_data;

    48

    49 if(filp->f_flags & O_NONBLOCK)

    50 return - EAGAIN;

    51

    52 /*休眠*/

    53 P_DEBUG("read data..... ");

    54 if(wait_event_interruptible(dev->test_queue, dev->cur_size > 0))

    55 return - ERESTARTSYS;

    56

    57 if (copy_to_user(buf, dev->kbuf, count)){

    58 ret = - EFAULT;

    59 }else{

    60 ret = count;

    61 dev->cur_size -= count;

    62 P_DEBUG("read %d bytes, cur_size:[%d] ", count, dev->cur_size);

    63 }

    64

    65 return ret; //返回实际写入的字节数或错误号

    66 }


    再来个应用程序:

    2 #include

    3 #include

    4 #include

    5 #include

    6

    7 int main(void)

    8 {

    9 char buf[20];

    10 int fd;

    11 int ret;

    12

    13 fd = open("/dev/test", O_RDWR | O_NONBLOCK);

    14 if(fd < 0)

    15 {

    16 perror("open");

    17 return -1;

    18 }

    19

    20 ret = read(fd, buf, 10);

    21 if (ret = -1) 检查错误的原因

    22 {

    23 perror("open");

    24 printf("errno = %d ", errno);

    25 }

    26 else

    27 {

    28 printf("buf is [%s] ", buf);

    29 }

    30

    31 close(fd);

    32 return 0;

    33 }


    验证一下:

    [root: 2nd]# ./app_read

    open: Resource temporarily unavailable

    errno = 29 //这就是-EAGAIN错误号返回给用户态的errno


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    四、总结


    上面讲了四个内容:

    1什么是休眠.

    2什么是等待队列

    3怎么通过等待队列把进程休眠

    4怎么唤醒进程


    其中有三处处扩展:

    1我只是实现了read的阻塞性IO,在一般的驱动中,write也是有阻塞功能的,大家可以尝试实现。

    2我只介绍了如何使用最简单的函数把进程休眠,在《linux设备驱动程序》有介绍高级休眠,其实就是细说wait_event的内部是用什么函数实现——我上面讲述的四个步骤。

    3.唤醒进程时的高级操作——独占等待。


    xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


    源代码:

     3rd_char_5.rar   
  • 相关阅读:
    Spring -- 自定义转换器
    Spring乱码问题解决方案
    Spring 声明式事务
    Spring -- IOC
    Spring--入门
    mybatis 二级缓存
    Mybatis 缓存失效的几种情况
    MyBatis深入浅出--入门
    【RF库Collections测试】Get From Dictionary
    【RF库Collections测试】Get Dictionary Values
  • 原文地址:https://www.cnblogs.com/huty/p/8518599.html
Copyright © 2020-2023  润新知