操作系统实验系列三: 字符设备驱动及进程间聊天程序
- Linux 动态模块
- Linux 设备驱动
- 字符驱动程序的实现
- 附录
1. LInux 动态模块
- Linux 动态模块概述
- 动态模块的结构
- 动态模块的实现细节
1.1 Linux 动态模块概述
与普通用户代码区别
- 动态模块无法使用 C 标准函数库,只能使用内核函数
- 内核模块的栈大小有限制(一般只有 4kb)
- 内核模块是一个由多个回调函数组成的”被动”代码集合体,采用了”事件驱动模型”
构建与使用流程
- 创建动态模块源码
- 修改 Makefile 文件生成编译规则
- 编译创建的模块源码,生成驱动模块
- 安装驱动模块
- 查看是否安装成功
- 使用驱动模块
- 卸载驱动模块
shell 命令
命令 | 作用 |
---|---|
insmod | 加载动态模块 |
lsmod | 显示已加载模块 |
rmmod | 卸载动态模块 |
1.2 动态模块的结构
源代码格式
1 |
|
Makefile 格式
1 | ifneq ($(KERNELRELEASE),) |
1.3 动态模块实现的细节
曾经用来测试系统调用的动态动态模块
为了不影响阅读放在了文件最后面
2. Linux 设备驱动
- Linux 设备驱动概述(PPT)
- Linux 设备驱动概述(博客总结)
2.1 Linux 设备驱动概述(PPT)
特点
- 为内核和其它子系统提供一个标准的接口
- 使用一些标准的内核服务,如内存服务
驱动程序与外界的接口
- 与内核的接口:通过数据结构 file_operations 来完成
- 与系统引导的接口:利用驱动程序对设备进行初始化
- 与设备的接口:描述驱动程序如何与设备进行交互
设备文件的属性
- 主设备号:
指名唯一的设备类型,即表示设备对应的驱动程序类型,它是块设备表和字符设备表中表项的索引。大多情况下,一个主设备号对应一个设备驱动程序- 次设备号:
用于在一组主设备号相同的设备之间唯一标识特定的设备,如两个相同的硬件就可用次设备号予以区分
代码结构
设备驱动程序是一个模块,内核用 file 结构标识设备驱动程序,内核使用 file_operations 结构来访问驱动程序中的函数
代码组成:驱动程序的注册与注销
设备的打开与释放
设备的读写操作
设备的控制操作
设备的中断和轮询处理
2.2 Linux 设备驱动概述(博客总结)
参考的博客:Linux 设备驱动之字符设备驱动
定义
只能一个字节一个字节地进行操作的设备
定义操作
引用自 CSDN 博主「andylauren」的原创文章: 原文链接
如上图,在 linux 内核中使用 cdev 结构体来描述字符设备,通过其成员 dev_t 来定义设备号,以确定字符设备的唯一性。通过其成员 file_operations 来定义字符设备驱动提供给 VFS 的接口函数,如常见的 open()、read()、write()等。
在 Linux 字符设备驱动中,模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号,通过 cdev_init( )建立 cdev 与 file_operations 之间的连接,通过 cdev_add( )向系统添加一个 cdev 以完成注册。模块卸载函数通过 cdev_del( )来注销 cdev,通过 unregister_chrdev_region( )来释放设备号。
用户空间访问该设备的程序通过 Linux 系统调用,如 open( )、read( )、write( ),来“调用”file_operations 来定义字符设备驱动提供给 VFS 的接口函数。
3. 字符驱动程序的实现
- 实验内容
- 实验分析
- 实验源代码
3.1 实验内容
编写一个简单的字符设备驱动程序
以内核空间模拟字符设备
完成对该设备的打开,读写和释放操作
编写聊天程序实现对该设备的同步和互斥操作
3.2 实验分析
- 驱动模块分析
- 聊天客户端分析
3.2.1 驱动模块分析
============字符驱动模块开始启动========
- 实现相关操作函数:open, read, write, close, ioctl
- 申请并分配设备号
- 创建设备节点(node 文件)
- 初始化 cdev(绑定 file_operations 与操作函数的具体实现)
- 注册 cdev
============字符驱动模块完成启动========
============字符驱动模块开始卸载========- 注销 cdev
- 删除设备节点(node 文件)
- 释放设备号
============字符驱动设备完成卸载========
3.2.2 聊天客户端分析
总体分析
- 每个客户端由两个子线程组成—读线程,写线程
- 每个客户端总是以一定频率尝试读取最新的消息,如果发现消息与原来的信息相同则舍弃不更新,否则更新消息
- 每个客户端在写的时候无法读取最新的消息
- 每个客户端发送的消息可以被所有客户端收到
具体实现
- 每个客户端可以看成是一个读者或者一个写者
- 正常情况下客户端以一定频率发送读取请求
- 客户端由 ctrl+\触发写者模式,读取用户输入并发送写入请求
- 由于客户端总是不断发送读取请求,所以总体模型应该是写者优先的
尝试的方法
刚开始尝试使用自旋锁进行同步操作,结果实在是太消耗资源了…(三个客户端进程就让整个电脑卡得不行不行的)
又尝试使用检测键盘按下来触发写者模式,结果既消耗资源又效果不佳(输入的时候乱七八糟的的)
遇到的问题
- sscanf 函数使用前需要清空缓冲区
- printf 函数最后需要输出\n 才能刷新输出缓冲区
- 为了实现进程同步,不能单纯地使用信号量机制,而需要信号量机制与共享内存共同配合
最终解决方案
- 采用单线程,客户端有两个模式——写者模式/读者模式
- 创建客户端的时候可以传入参数用来初始化同步变量
- 使用共享内存和信号量的方案来解决进程间的同步问题
- (共享内存可以进行进程间通信,信号量可以用来解决线程间同步)
3.3 实验源代码
- 驱动模块
- 客户端模块
3.3.1 驱动模块
源代码
1
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325//=========================
// author: bibibi
// description: 字符设备驱动模块
//=========================
// > ============字符驱动模块开始启动========
// > 1. 实现相关操作函数:open, read, write, close, ioctl
// > 2. 申请并分配设备号
// > 3. 创建设备节点(node文件)
// > 4. 初始化cdev(绑定file_operations与操作函数的具体实现)
// > 5. 注册cdev
// > ============字符驱动模块完成启动========
// > ============字符驱动模块开始卸载========
// > 6. 注销cdev
// > 7. 删除设备节点(node文件)
// > 8. 释放设备号
// > ============字符驱动设备完成卸载========
//描述设备的结构体
// struct cdev {
// struct kobject kobj; 每个 cdev 都是一个 kobject
// struct module *owner; 指向实现驱动的模块
// const struct file_operations *ops; 操纵这个字符设备文件的方法
// struct list_head list; 与 cdev 对应的字符设备文件的 inode->i_devices 的链表头
// dev_t dev; 起始设备编号
// unsigned int count; 设备范围号大小
// };
//=======================宏定义与功能函数声明===================
//定义设备号,如果定义为0,则动态申请设备号
//定义子设备的数量
//定义字符设备的缓冲区的大小
//相关操作函数声明
static int bibibi_open(struct inode *inode, struct file *filp); //打开字符设备
static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos); //读取字符设备(一个字节)
static ssize_t bibibi_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos); //写入字符设备(一个字节)
static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence); //控制指针位置
static int bibibi_release(struct inode *inode, struct file *filp); //关闭字符设备
static int bibibi_ioctl(int fd, int cmd); //控制字符设备
//=======================相关的数据结构定义===================
//驱动子设备的结构体
struct bibibi_device{
char *data; //缓冲区指针
unsigned long size; //缓冲区大小变量
};
//驱动子设备结构体数组的指针
struct bibibi_device *bibibi_devices;
//设备节点文件指针
struct class *bibibi_class;
//驱动模块的cdev
struct cdev bibibi_cdev;
//主设备号
static int bibibi_major=BIBIBI_DEV_MAJOR;
//设定模块向内核传递的参数
//可以通过这一个参数来在加载模块的时候决定设备号,默认设备号为333
module_param(bibibi_major,int, S_IRUGO);
//模块操作定义结构体
static const struct file_operations bibibi_operations={
.owner=THIS_MODULE,
.llseek=bibibi_llseek,
.read=bibibi_read,
.write=bibibi_write,
.open=bibibi_open,
//.ioctl=bibibi_ioctl, 报错说没有这一个成员,先注释掉
.release=bibibi_release,
};
//初始化字符驱动模块
static int __init init_char_driver(void){
//2. 申请并分配设备号
//如果指定了设备号,采用静态分配设备号
//如果没有指定,采用动态分配设备号
int result=0; //用来接收一些函数的返回值
dev_t device_num=MKDEV(bibibi_major, 0); //将主次设备号合成一个设备号
//申请设备号
if(bibibi_major){
//静态申请
result=register_chrdev_region(device_num, BIBIBI_DEV_NUM, "bibibi_char_dev");
}
else{
//动态申请
result = alloc_chrdev_region(&device_num, 0, BIBIBI_DEV_NUM, "bibibi_char_dev");
bibibi_major = MAJOR(device_num);
}
//申请设备号失败
if(result<0) return result;
//3. 创建设备节点(node文件)
//静态申请BIBIBI_DEV_NUM个节点 标志为"进程上下文,可以睡眠"
bibibi_devices=kmalloc(BIBIBI_DEV_NUM * sizeof(struct bibibi_device), GFP_KERNEL);
if(!bibibi_devices){
//创建失败,释放 设备号并返回
result = -ENOMEM;
unregister_chrdev_region(device_num, BIBIBI_DEV_NUM);
return result;
}
// //创建设备文件
// bibibi_class = class_create(THIS_MODULE, "bibibi_class");
// printk("error type: %d",IS_ERR(bibibi_class));
// result = -ENOMEM;
// unregister_chrdev_region(device_num, BIBIBI_DEV_NUM);
// return result;
// if(IS_ERR(bibibi_class)){
// printk("Err: failed in creating class.\n");
// return -1;
// }
// device_create(bibibi_class, NULL, MKDEV(bibibi_major, 0), "bibibi_device",0);
//填充已经申请的内存空间为0
//void * memset (void *s ,int c, size_t n);
memset(bibibi_devices,0,BIBIBI_DEV_NUM * sizeof(struct bibibi_device));
int i=0;
for(i=0;i<BIBIBI_DEV_NUM;i++){
//赋值缓冲区长度变量并填充空间 标志为"进程上下文,可以睡眠"
bibibi_devices[i].size=BIBIBI_DEV_SIZE;
bibibi_devices[i].data=kmalloc(BIBIBI_DEV_SIZE,GFP_KERNEL );
memset(bibibi_devices[i].data,0,BIBIBI_DEV_SIZE);
}
//4. 初始化cdev(绑定file_operations与操作函数的具体实现)
cdev_init(&bibibi_cdev,&bibibi_operations);
bibibi_cdev.owner=THIS_MODULE;
bibibi_cdev.ops=&bibibi_operations;
//5. 注册cdev
cdev_add(&bibibi_cdev,MKDEV(bibibi_major, 0),BIBIBI_DEV_NUM);
//驱动模块启动成功
printk("device init success\n");
return 0;
}
//清理工作
static void __exit exit_char_driver(void){
// > 6. 注销cdev
// > 7. 删除设备节点(node文件)
// > 8. 释放设备号
//6. 注销cdev
cdev_del(&bibibi_cdev);
//7. 删除设备节点
kfree(bibibi_devices);
//删除磁盘设备文件
// device_destroy(bibibi_class, MKDEV(bibibi_major, 0));
// class_destroy(bibibi_class);
//8. 释放设备号
unregister_chrdev_region(MKDEV(bibibi_major, 0), 2);
//驱动模块卸载完毕
printk(KERN_INFO "device exit success");
}
//相关参数的具体操作
// static int bibibi_open(struct inode *inode, struct file *filp); //打开字符设备
// static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos); //读取字符设备(一个字节)
// static ssize_t bibibi_write(struct file *filp, const char__user *buf, size_t size, loff_t *pos); //写入字符设备(一个字节)
// static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence); //控制指针位置
// static int bibibi_release(struct inode *inode, struct file *filp); //关闭字符设备
// static int bibibi_ioctl(int fd, int cmd); //控制字符设备
static int bibibi_open(struct inode *inode, struct file *filp){
struct bibibi_device *device;
//判断是否已经达到了支持的子设备的上限
int num=MINOR(inode->i_rdev);
if(num>=BIBIBI_DEV_NUM)
return -ENODEV;
//分配设备
//device=&bibibi_devices[num];
//为了实现聊天室,不同的设备请求返回同一个子设备
//互斥和同步由客户端完成
device=&bibibi_devices[0];
filp->private_data=device;
return 0;
}
static ssize_t bibibi_read(struct file *filp, char __user *buf, size_t size, loff_t *pos){
unsigned long position=*pos;
unsigned int count=size;
int result;
struct bibibi_device *device=filp->private_data;
//如果要求位置非法则直接退出
if(position>=BIBIBI_DEV_SIZE)
return 0;
//调整读取数据长度
if(count>BIBIBI_DEV_SIZE-position)
count=BIBIBI_DEV_SIZE-position;
//传送数据给用户
if(copy_to_user(buf, (void*)(device->data + position), count)) {
result = -EFAULT;
} else {
*pos += count;
result = count;
printk(KERN_INFO "read %d bytes from %lu\n", count, position);
}
return result;
}
static ssize_t bibibi_write(struct file *filp, const char __user *buf, size_t size, loff_t *pos){
unsigned long position = *pos;
unsigned int count = size;
int result = 0;
struct bibibi_device *device = filp->private_data;
//确保写入安全
if (position >= BIBIBI_DEV_SIZE)
return 0;
if (count > BIBIBI_DEV_SIZE-position)
count = BIBIBI_DEV_SIZE - position;
//写入数据
if (copy_from_user(device->data + position, buf, count)) {
result = -EFAULT;
} else {
*pos += count;
result = count;
printk(KERN_INFO "write %d bytes from %lu\n", count, position);
}
return result;
}
static loff_t bibibi_llseek(struct file *filp, loff_t offset, int whence){
loff_t newpos;
//选择操作类型
switch (whence) {
//指定新位置
case 0:
newpos = offset;
break;
//后移指针
case 1:
newpos = filp->f_pos + offset;
break;
//反向指定新位置
case 2:
newpos = BIBIBI_DEV_SIZE - 1 + offset;
break;
default:
return -EINVAL;
}
if ((newpos < 0) || (newpos > BIBIBI_DEV_SIZE))
return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
static int bibibi_release(struct inode *inode, struct file *filp){
//释放子设备操作
//其实没什么需要做的
return 0;
}
static int bibibi_ioctl(int fd, int cmd){
//暂时不支持操纵设备
return 0;
}
//=======================驱动模块声明==========================
module_init(init_char_driver);
module_exit(exit_char_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yishiyu");Makefile
1
2
3
4
5
6
7
8
9
10
11obj-m := char_driver.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
CONFIG_MODULE_SIG=n
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions部署与退出脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19运行脚本
!/bin/bash
sudo make
sudo insmod char_driver.ko bibibi_major=181
sudo mknod /dev/bibibi_device c 181 0
mknod 创建特殊文件 c表示面向字符
mknod Name { b | c } Major Minor
删除设备文件可以直接使用rm -f 设备文件名
rm -f memdev0
退出脚本
!/bin/bash
sudo rmmod char_driver
sudo rm -f /dev/bibibi_device3.3.2 客户端模块
源代码
1
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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240//=========================
// author: bibibi
// description: 使用字符设备通信
//=========================
//使用bibibi_device字符设备
//实现一个可以实现一对多通信的客户端
//每个客户端地位相等,每个客户端开启时需要进行一个命名
//每个客户端发送一个信息的时候其他所有客户端都会收到信息
//使用写者读者模型,写者优先
//
//每个客户端可以执行两个操作——读或者写
//每次写操作将覆盖设备全部的缓冲区
//
//用户名变量(暂时不确保所有用户不重名)
static char my_name[16];
static char buf[MAX_BUFFER]={0};
static char prebuf[MAX_BUFFER]={0};
static char exit_signal[]="quit";
static int fd=0;
//更改客户端模式
static int running=1;
static int mode=READER;
void change_mode();
void client_write();
void client_read();
// wmutex:semaphore=1 //读者与写者之间、写者与写者之间互斥使用共享数据
// S:semaphore=1 //当至少有一个写者准备访问共享数据时,它可使后续的读者等待写完成
// S2:semaphore=1 //阻塞第二个以后的等待读者
// Readcount,writecount: int = 0,0; //当前读者数量、写者数量
// mutex1 :semaphore = 1 //多个读者互斥使用 readcount
// mutex2 :semaphore = 1 //多个写者互斥使用 writecount
//同步信号量
struct client_syn{
sem_t data_available; //操作权限 相当于wmutex
sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
int reader_count; //读者计数器
int writer_count; //写者计数器
sem_t reader_count_en; //读者计数器操作信号量
sem_t writer_count_en; //读者计数器操作信号量
}client_syn;
static struct client_syn *syn_var;
//单进程客户端
int main(char argc,char *argv[]){
signal(SIGQUIT , change_mode); //设置键盘中断
//设置公共信号量
int shared_mem = shmget((key_t)SHARED_MEM, sizeof(client_syn), 0666 | IPC_CREAT);
void *shm = shmat(shared_mem, 0, 0);
syn_var=(struct client_syn*)shm;
//如果有参数则初始化公共信号量
if(argc>1){
//每次只能有一个进程进行操作
sem_init(&(syn_var->data_available), 1, 1);
sem_init(&(syn_var->writer_syn), 1, 1);
sem_init(&(syn_var->reader_syn), 1, 1);
sem_init(&(syn_var->reader_count_en), 1, 1);
sem_init(&(syn_var->writer_count_en), 1, 1);
//初始化chengggong
printf("init success!");
}
//获取用户名称
printf("please input your name:\t");
// scanf("%10[a-z]",&my_name);
char temp[3]=":";
scanf("%10[a-z]",my_name);
strncat(&my_name,temp,2);
//打开文件
fd = open("/dev/bibibi_device",O_RDWR);
if (fd == -1) {
printf("open bibibi_device failed!\n");
return -1;
}
printf("====================client %s start================\n",my_name);
while(running){
sleep(1); //更新频率为1Hz
switch (mode){
case WRITER:
client_write();
break;
case READER:
client_read();
break;
default:
break;
}
}
printf("====================client %s exit================\n",my_name);
//清理工作
return 0;
}
//切换模式
void change_mode(){
mode=1-mode;
printf("\b\bplease type in\n");
}
//写入函数
void client_write(){
// P(mutex2); // 申请访问 writecount
// if writecount=0 then P(S); // 写者进程进入等待队列
// writecount++;
// V(mutex2); // 释放 writecount
// P(wmutex); // 是否有读者或者写者正在操作
// writing …
// V(wmutex); // 写操作完成
// P(mutex2);
// writecount--;
// if writecount=0 then V(S); // 没有写者在等待,允许读者进程进入操作
// V(mutex2);
// sem_t data_available; //操作权限 相当于wmutex
// sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
// sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
// int reader_count; //读者计数器
// int writer_count; //写者计数器
// sem_t reader_count_en; //读者计数器操作信号量
// sem_t writer_count_en; //读者计数器操作信号量
//同步操作
sem_wait(&(syn_var->writer_count_en));
if(syn_var->writer_count==0) sem_wait(&(syn_var->writer_syn)); //阻塞读者操作
syn_var->writer_count++;
sem_post(&(syn_var->writer_count_en));
sem_wait(&(syn_var->data_available));
//读取输入
printf("%s",my_name);
setbuf(stdin,NULL);
scanf("%50[a-z]",buf);
if(strcmp(buf,exit_signal)==0){
printf("client quit\n");
running=0;
}
write(fd,my_name,strlen(my_name));
lseek(fd, strlen(my_name), SEEK_SET);
write(fd,buf, sizeof(buf));
lseek(fd, 0, SEEK_SET);
read(fd, prebuf, sizeof(prebuf));
lseek(fd, 0, SEEK_SET);
mode=READER;
sem_post(&(syn_var->data_available));
sem_wait(&(syn_var->writer_count_en));
syn_var->writer_count--;
if(syn_var->writer_count==0) sem_post(&(syn_var->writer_syn)); //释放读者操作
sem_post(&(syn_var->writer_count_en));
}
//读取函数
void client_read(){
// P(S2); // 是否已经有读者进程在等待队列中
// P(S); // 是否有写者进程在等待队列中
// P(mutex1); // 没有其他的读者进程在访问 readcount
// if readcount=0 then P(wmutex); // 判断是否有写者进程在写
// readcount++;
// V(mutex1); // 释放 readcount
// V(S); // 允许写者进程等待
// V(S2); // 允许读者进程等待
// reading …
// P(mutex1);
// readcount--;
// if readcount=0 then V(wmutex); // 允许写者进程写
// V(mutex1); // 释放 readcount
// sem_t data_available; //操作权限 相当于wmutex
// sem_t writer_syn; //写者操作时的阻塞信号量 相当于S
// sem_t reader_syn; //读者操作时的阻塞信号量 相当于S2
// int reader_count; //读者计数器
// int writer_count; //写者计数器
// sem_t reader_count_en; //读者计数器操作信号量
// sem_t writer_count_en; //读者计数器操作信号量
//同步操作
sem_wait(&(syn_var->reader_syn));
sem_wait(&(syn_var->writer_syn));
sem_wait(&(syn_var->reader_count_en));
if(syn_var->reader_count==0) sem_wait(&(syn_var->data_available)); //读的时候禁止写入
syn_var->reader_count++;
sem_post(&(syn_var->reader_count_en));
sem_post(&(syn_var->writer_syn));
sem_post(&(syn_var->reader_syn));
//读取文件
read(fd, buf, sizeof(buf));
lseek(fd, 0, SEEK_SET);
//如果两次内容不完全一致则输出
if(strcmp(buf,prebuf)!=0){
printf("%s\n",buf);
strncpy(prebuf,buf,MAX_BUFFER);
}
sem_wait(&(syn_var->reader_count_en));
syn_var->reader_count--;
if(syn_var->reader_count==0) sem_post(&(syn_var->data_available)); //释放写权限
sem_post(&(syn_var->reader_count_en));
}Makefile
1
2
3
4
5all:
gcc client.c -o client.o -g -lpthread
clean:
rm client.o部署脚本
1
2
3
4!/bin/bash
sudo make
sudo ./client.o4. 附录
- 用来测试系统调用的动态模块
4.1 用来测试系统调用的动态模块
1 |
|