02、阻塞 IO 实验 *
阻塞 IO 在 Linux内核中是非常常用的 IO 模型, 所依赖的机制是等待队列。 下面让我们来开始阻塞 IO 的学习吧。
一、什么是等待队列
在Linux系统中,等待队列是管理进程暂停和唤醒的机制。它的结构就像一个环形双向列表,主要由两部分组成:
- 队列头(wait_queue_head_t) 这是队列的起点,负责管理整个列表。它的结构很简单,包含两个关键部分:
- 自旋锁(spinlock_t lock):用来保证多个处理器操作队列时不会互相干扰
- 链表头(list_head task_list):指向队列中第一个等待任务的位置
- 队列元素 每个元素代表一个等待的进程,按顺序连在队列头的链表里。
工作原理很简单: 当进程需要等待某个条件(比如数据到达或资源可用),就会把自己的任务信息加入队列暂停执行; 当条件满足时,内核会从队列里唤醒对应的任务继续运行。
系统用typedef把结构体名称简化为wait_queue_head_t,这样开发者调用时更方便。整个机制就像一个"等待名单",让进程能有序地暂停和恢复。
struct _wait_queue_head{
spinlock_t lock; //自旋锁
struct list_head task_list //链表头
};
typefef struct _wait_queue_head wait_queue_head_t;
2
3
4
5
等待队列项使用结构体 wait_queue_t 来表示, 等待队列项是等待队列元素, 该结构体同样定义在文件 include/linux/wait.h 里面, 结构体内容如下所示:
struct _wait_queue{
unsigned int flags;
void *private;
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct _wait_queue wait_queue_t;
2
3
4
5
6
7
二、等待队列 API 函数
2.1、定义并初始化等待队列头
要使用等待队列,首先需要初始化它的队列头。这可以通过两种方法实现:
方法一:
用DECLARE_WAIT_QUEUE_HEAD宏直接声明队列头。这个宏的作用就是在声明变量的同时自动完成等待队列的初始化,例如: DECLARE_WAIT_QUEUE_HEAD(my_queue);
这样一行代码就完成了队列头的静态创建和初始化工作。
方法二
使用 init_waitqueue_head 宏动态初始化等待队列头,宏定义如下:
参数q表示需要初始化的队列头指针。使用宏定义如下所示
wait_queue_head_t head; //等待队列头
init waitqueue head(&head);//初始化等待队列头指针
2
然后再来学习如何创建等待队列元素,也就是等待队列项,
2.2、创建等待队列项
在Linux系统中,DECLARE_WAITQUEUE是一个用来创建进程等待项的宏。它的使用方法很简单:
DECLARE_WAITQUEUE(队列名称, current);
这个宏的两个参数:
- 第一个参数是给新创建的等待项起个名字(比如waiter)
- 第二个参数必须是current,这是个特殊变量,永远指向当前正在运行的进程
具体来说:当你调用这个宏时,系统会自动为当前运行的进程创建一个等待队列项。这个等待项会和当前进程绑定在一起,用于让进程在等待某些事件时进入睡眠状态。
举个例子:
DECLARE_WAITQUEUE(my_waiter, current);
这行代码的作用就是:为当前进程创建一个名为my_waiter的等待项,方便后续让进程在需要时等待资源。
2.3、添加/删除队列
当设备没有准备就绪(如没有可读数据)而需要进程阻塞的时候,就需要将进程对应的等
待队列项添加到前面创建的等待队列中,只有添加到等待队列中以后进程才能进入休眠态。当
设备可以访问时(如有可读数据),再将进程对应的等待队列项从等待队列中移除即可。
等待队列项添加队列函数如下所示:
::: 函数原型:
void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
函数功能:
(通过等待队列头)向等待队列中添加队列项
参数含义:
wq_head 表示等待队列项要加入等待队列的等待队列头
wq_entry 表示要加入的等待队列项
函数返回值
无 :::
等待队列项移除队列函数如下所示:
::: 函数原型:
void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
函数功能:
要删除的等待队列项所处的等待队列头
函数含义:
第一个参数 q 表示等待队列项要加入等待队列的等待队列头
第二个参数 wait 表示要加入的等待队列项
函数返回值:
无 :::
2.4、等待事件
除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒
等待队列中的进程,使用如下所示的宏,是不可中断的阻塞等待。
#define __wait_event(wq_head, condition) \
(void)___wait_event(wq_head, condition, TASK_UNINTERRUPTIBLE, 0, 0, \
schedule())
2
3
::: 宏定义功能:
不可中断的阻塞等待,让调用进程进入不可中断的睡眠状态,在等待队列里面睡眠直到
condition 变成真,被内核唤醒。
参数含义:
第一个参数 wq: wait_queue_head_t 类型变量
第二个参数 condition : 等待条件,为假时才可以进入休眠。如果 condition 为真,则不会
休眠 :::
除此之外,wait_event_interruptible 的宏是可中断的阻塞等待。
#define __wait_event_interruptible(wq_head, condition) \ ___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0, \
schedule())
2
::: 宏含义功能:
可中断的阻塞等待,让调用进程进入可中断的睡眠状态,直到 condition 变成真被内核唤醒
或信号打断唤醒。
参数含义:
第一个参数 wq :wait_queue_head_t 类型变量
第二个参数 condition :等待条件。为假时才可以进入休眠。如果 condition 为真,则不会休
眠。 :::
wait_event_timeout() 宏也与 wait_event()类似.不过如果所给的睡眠时间为负数则立即返
回.如果在睡眠期间被唤醒,且 condition 为真则返回剩余的睡眠时间,否则继续睡眠直到到达或
超过给定的睡眠时间,然后返回 0。
wait_event_interruptible_timeout() 宏与 wait_event_timeout()类似,不过如果在睡眠期间被
信号打断则返回 ERESTARTSYS 错误码。
wait_event_interruptible_exclusive() 宏同样和 wait_event_interruptible()一样,不过该睡眠
的进程是一个互斥进程
注意:调用的时要确认 condition 值是真还是假,如果调用 condition 为真,则不会休眠。
2.5、等待队列唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下俩个函数:
::: 函数原型:
wake_up(wait_queue_head_t *q)
函数功能:
唤醒所有休眠进程
参数含义:
q 表示要唤醒的等待队列的等待队列头
函数原型:
wake_up_interruptible(wait_queue_head_t *q)
函数功能:
唤醒可中断的休眠进程
参数含义:
q 表示要唤醒的等待队列的等待队列头 :::
三、等待队列使用方法
步骤一: 初始化等待队列头, 并将条件置成假(condition=0)。
步骤二: 在需要阻塞的地方调用 wait_event(), 使进程进入休眠状态。
步骤三: 当条件满足时, 需要解除休眠, 先将条件(condition=1),然后调用 wake_up 函数唤醒等待队列中的休眠进程。
四、实验代码
4.1、驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/wait.h>
struct device_test{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
int flag; //标志位
};
struct device_test dev1;
DECLARE_WAIT_QUEUE_HEAD(read_wq); //定义并初始化等待队列头
/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
printk("This is cdev_test_open\r\n");
return 0;
}
/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
printk("copy_from_user error\r\n");
return -1;
}
test_dev->flag=1;//将条件置1
wake_up_interruptible(&read_wq); //并使用wake_up_interruptible唤醒等待队列中的休眠进程
return 0;
}
/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;
wait_event_interruptible(read_wq,test_dev->flag); //可中断的阻塞等待,使进程进入休眠态
if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
{
printk("copy_to_user error\r\n");
return -1;
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
return 0;
}
/*设备操作函数*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = cdev_test_open, //将open字段指向chrdev_open(...)函数
.read = cdev_test_read, //将open字段指向chrdev_read(...)函数
.write = cdev_test_write, //将open字段指向chrdev_write(...)函数
.release = cdev_test_release, //将open字段指向chrdev_release(...)函数
};
static int __init chr_fops_init(void) //驱动入口函数
{
/*注册字符设备驱动*/
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
printk("major is %d \r\n", dev1.major); //打印主设备号
printk("minor is %d \r\n", dev1.minor); //打印次设备号
/*2 初始化cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
/*3 添加一个cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
/*4 创建类*/
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
return 0;
err_device_create:
class_destroy(dev1.class); //删除类
err_class_create:
cdev_del(&dev1.cdev_test); //删除cdev
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
return ret;
}
static void __exit chr_fops_exit(void) //驱动出口函数
{
/*注销字符设备*/
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
cdev_del(&dev1.cdev_test); //删除cdev
device_destroy(dev1.class, dev1.dev_num); //删除设备
class_destroy(dev1.class); //删除类
}
module_init(chr_fops_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
4.2、APP
read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int fd;
char buf1[32] = {0};
char buf2[32] = {0};
fd = open("/dev/test", O_RDWR); //打开led驱动
if (fd < 0)
{
perror("open error \n");
return fd;
}
printf("read before \n");
read(fd,buf1,sizeof(buf1)); //从/dev/test文件读取数据
printf("buf is %s \n",buf1);
printf("read after \n");
close(fd); //关闭文件
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
WRITE
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd;
char buf1[32] = {0};
char buf2[32] = "nihao";
fd = open("/dev/test", O_RDWR); //打开led驱动
if (fd < 0)
{
perror("open error \n");
return fd;
}
printf("write before \n");
write(fd,buf2,sizeof(buf2)); //向/dev/test文件写入数据
printf("write after\n");
close(fd); //关闭文件
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25