系统调用与进程通信

本文最后更新于:1 小时前

# 系统调用与进程通信

# 系统调用

# 查询 sys_call_table 的地址

通过命令:

1
sudo cat /proc/kallsyms | grep sys_call_table

查找到 sys_call_table 的地址为 0xffffffffb48001e0

sys_call_table地址

# 查询可用的系统调用号

通过命令打开 unistd_32.h

1
vim /usr/include/asm/unistd_32.h

如图所示,可以看到 387 号是可用的系统调用号:

unistd_32.h

# 创建 dzc_test.c 文件

创建 dzc_test.c 文件,在文件内定义系统调用,其中使用到的 sys_call_table 地址和可用系统调用号为上述的 0xffffffffb48001e0 以及 387

代码如下:

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
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/unistd.h>
#include <linux/sched.h>

MODULE_LICENSE("Dual BSD/GPL");
//sys_call_table地址
#define SYS_CALL_TABLE_ADDRESS 0xffffffffb48001e0
//系统调用号为387
#define NUM 387
//用来存储cr0寄存器原来的值
int orig_cr0;
unsigned long *sys_call_table_my=0;
//定义一个函数指针,用来保存一个系统调用
static int(*anything_saved)(void);
//使cr0寄存器的第17位设置为0(内核空间可写)
static int clear_cr0(void)
{
unsigned int cr0=0;
unsigned int ret;
asm volatile("movq %%cr0,%%rax":"=a"(cr0));
ret=cr0;
cr0&=0xfffffffffffeffff;
asm volatile("movq %%rax,%%cr0"::"a"(cr0));
return ret;
}

//使cr0寄存器设置为内核不可写
static void setback_cr0(int val)
{
asm volatile("movq %%rax,%%cr0"::"a"(val));
}

//定义自己的系统调用
asmlinkage long sys_mycall(void)
{
//在这里输出我的学号和姓名
printk("20192121026_邓智超\n");
return current->pid;
}

static int __init call_init(void)
{
sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS);
printk("开始执行系统调用\n");
//保存系统调用表中的NUM位置上的系统调用
anything_saved=(int(*)(void))(sys_call_table_my[NUM]);
//使内核地址空间可写
orig_cr0=clear_cr0();
//用自己的系统调用替换NUM位置上的系统调用
sys_call_table_my[NUM]=(unsigned long) &sys_mycall;
//使内核地址空间不可写
setback_cr0(orig_cr0);
return 0;
}

static void __exit call_exit(void)
{
printk("系统调用结束\n");
orig_cr0=clear_cr0();
//将系统调用恢复
sys_call_table_my[NUM]=(unsigned long)anything_saved;
setback_cr0(orig_cr0);
}

module_init(call_init);
module_exit(call_exit);

# 创建 Makefile 文件

查询到内核源码的路径为 **/usr/src/kernels/4.18.0-193.14.2.el8_2.x86_64**

使用 vim Makefile 命令创建 Makefile 文件,并在其中添加内容为:

1
2
3
4
5
6
7
obj-m:=dzc_test.o
CURRENT_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/usr/src/kernels/4.18.0-193.14.2.el8_2.x86_64
all:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean

# 安装内核模块

执行命令 make,并通过命令 **ls | grep ‘dzc_test.*’** 查看是否执行成功

make结果

使用如下命令插入模块:

1
insmod dzc_test.ko

使用如下命令检查插入是否成功:

1
lsmod

插入结果

可以看到名为 dzc_test 的模块,说明插入成功。

# 测试

创建 test.c 并在其中添加代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
#include<stdlib.h>
#include<linux/kernel.h>
#include<sys/syscall.h>
#include<unistd.h>

int main()
{
unsigned long x = 0;
//测试387号调用
x = syscall(387);
printf("20192121026_邓智超:%d\n", x);
return 0;
}

使用 gcc test.c./a.out 命名查看运行结果:

运行结果

通过 dmesg 命令查看系统调用的结果:

dmesg命令

可见输出 "20192121026_邓智超",说明添加系统调用成功

# 进程通信

# 代码实践 1

# 信号 - SIGNAL

创建 signal.c 文件,并在其中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
int k;
void int_func(int sig)
{
k = 0;
printf("int_func\n");
}
int main()
{
signal(SIGINT, int_func);
k = 1;
while(k == 1)
{
printf("Hello!\n");
}
printf("OK!\n");
exit(0);
}

通过 gcc signal.c 和 **./a.out** 命令运行得到以下结果:

signal运行结果

# 管道 - PIPE

创建 pipe.c 文件,并在其中添加以下代码:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int p1,fd[2];
//定义读缓冲区
char outpipe[50];
//定义写缓冲区
char inpipe[50];
//创建无名管道fd
pipe(fd);
while((p1 = fork()) == -1);
//子进程返回
if(p1 == 0)
{
strcpy(inpipe, "This is a message!");
//写信息到管道
write(fd[1], inpipe, 50);
exit(0);
}
else//父进程返回
{
//等待子进程终止
wait(0);
//从管道读信息到缓冲区
read(fd[0], outpipe, 50);
//显示读到的信息
printf("%s\n",outpipe);
exit(0);
}
}

通过 gcc pipe.c 和 **./a.out** 命令运行得到以下结果:

pipe运行结果

# 消息传递

创建 Sndfile.c 文件,并在其中添加以下代码:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<linux/msg.h>
#define MAXMSG 512
//定义消息缓冲区数据结构
struct my_msg
{
long int my_msg_type;
int i;
char some_text[MAXMSG];
}msg;

int main()
{
//定义消息换冲突内部标识
int msgid;
//定义用户缓冲区
char buffer[BUFSIZ];
//创建消息队列,key为12
msgid = msgget(12,0666|IPC_CREAT);
while (1)
{
//提示键入消息内容
puts ( "Enter some text:");
//标准输入送buffer
fgets (buffer,BUFSIZ,stdin);
msg.i++;
printf("i=%d\n" , msg.i);
msg.my_msg_type=3;
//buffer中的内容送信息缓冲
strcpy(msg.some_text,buffer);
//发送消息到消息队列
msgsnd(msgid, &msg,MAXMSG,0);
//消息为end结束
if(strncmp(msg.some_text, "end",3)==0)
break;
}
exit(0);
}

通过 gcc Sndfile.c 和 **./a.out** 命令运行得到以下结果:

Sndfile运行结果

创建 Recfile.c 文件,并在其中添加以下代码:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include <linux/msg.h>

#define MAXMSG 512
//定义消息换冲突数据结构
struct my_msg
{
long int my_msg_type;
int i;
char some_text[MAXMSG];
}msg;

int main()
{
int msgid;
msg.my_msg_type=3;
//获取消息队列,key为1234
msgid=msgget(12,0666|IPC_CREAT);
while(1)
{
//接收消息
msgrcv(msgid,&msg,BUFSIZ,msg.my_msg_type,0);
//显示消息
printf("You wrote:%s and i=%d\n",msg.some_text,msg.i);
//消息为end则结束
if(strncmp(msg.some_text, "end" ,3)==0)
break;
}
//删除消息队列
msgctl(msgid,IPC_RMID,0);
exit (0);
}

通过 gcc Recfile.c 和 **./a.out** 命令运行得到以下结果:

Recfile运行结果

# 共享内存

创建 Sndshm.c 文件,并在其中添加以下代码:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<linux/shm.h>
int main()
{
int shmid;
char *viraddr;
char buffer[BUFSIZ];
//创建共享内存
shmid = shmget(1234, BUFSIZ, 0666 | IPC_CREAT);
//附接到共享内存
viraddr = (char*)shmat(shmid, 0, 0);
while(1)
{
//提示用户输入信息
puts("Enter some text:\n");
//将标准输入送入到缓冲区中
fgets(buffer, BUFSIZ, stdin);
//采用追加方式写到共享内存
strcat(viraddr, buffer);
//当输入的字符串为"end"时,终止循环
if(strncmp(buffer, "end", 3) == 0)
break;
}
//切断与共享内存的链接
shmdt(viraddr);
exit(0);
}

通过 gcc Sndshm.c 和 **./a.out** 命令运行得到以下结果:

Sndshm运行结果

创建 Revshm.c 文件,并在其中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/shm.h>

int main()
{
int shmid;
char *viraddr;
//创建共享内存
shmid=shmget(1234, BUFSIZ, 0666 | IPC_CREAT);
//附接到共享内存
viraddr=(char*)shmat(shmid, 0, 0);
//输出共享内存的内容
printf("Your message is :\n%s", viraddr);
//切断与共享内存的链接
shmdt(viraddr);
//释放共享内存
shmctl(shmid,IPC_RMID, 0);
exit(0);
}

通过 gcc Revshm.c 和 **./a.out** 命令运行得到以下结果:

Revshm运行结果

# 代码实践 2

# 思路

  • 为 M1,M2 分别建立两个共享空间,创建三个进程,父进程,A 进程,B 进程,将共享内存连接到当前进程的地址空间,使得三个进程都能访问 M1,M2。
  • 设置两个信号量 A_ENABLE,B_ENABLE,分别表示允许 A 执行和允许 B 执行。程序开始的时候,A_ENABLE = 1,B_ENABLE = 0。每次 A 执行时,对 A_ENABLE 执行 P 操作,A 结束时,对 B_ENABLE 执行 V 操作;每次 B 执行时,对 B_ENABLE 执行 P 操作,B 结束时,对 A_ENABLE 执行 V 操作,这样就可以达到 A 和 B 轮流执行的效果。
  • 为了使程序能够有较好的效果且能够停止,这里设置 A 和 B 分别执行 4 次操作
  • 此代码参考生产者消费者问题 多进程共享内存 - LightningStar - 博客园 (cnblogs.com) 并按照题目要求做了修改以符合作业要求

# 代码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

#define MAX_NUM 4096

//共享内存缓冲区资源的循环队列
struct Resource
{
int message[MAX_NUM];
int front;
int rear;
};

//M1 M2
struct Resource M[2];

//信号量数据结构
union semun
{
int value;
};

//用于映射信号量编号与对应意义的映射
enum MUTEX
{
//是否允许A操作
A_ENABLE,
//是否允许B操作
B_ENABLE,
//信号量相关
NUM_MUX
};

//信号量集合描述符
int semid;

//共享空间描述符,M1,M2各占一个空间
int shmid[2];

//共享空间首地址
struct Resource *shm[2];

//绑定共享内存
void attach_shm()
{
//将共享内存连接到当前进程的地址空间
shm[0] = (struct Resource *)shmat(shmid[0], NULL, 0);
shm[1] = (struct Resource *)shmat(shmid[1], NULL, 0);
if (shm[0] == (struct Resource *)-1 || shm[1] == (struct Resource *)-1)
{
fprintf(stderr, "shmat failed\n");
exit(EXIT_FAILURE);
}
}

//解绑共享内存
void detach_shm()
{
//把共享内存从当前进程中分离
if (shmdt((void *)shm[0]) == -1 || shmdt((void *)shm[1]) == -1)
{
fprintf(stderr, "shmdt failed\n");
exit(EXIT_FAILURE);
}
}

//获取资源,对应信号量实施P操作
void P(short unsigned int num)
{
struct sembuf sb =
{
num, -1, 0 //0表示信号量编号,-1表示P操作,SEM_UNDO表示进程退出后,该进程对sem进行的操作将被撤销
};
//修改集合中,一个或多个信号量值
semop(semid, &sb, 1);
}

//释放资源,对应信号量实施V操作
void V(short unsigned int num)
{
struct sembuf sb = {
num, 1, 0 //
};
semop(semid, &sb, 1);
}

//读取数据,id为0,表示A在操作,id为2,表示B在操作
int readMessage(int id)
{
// id = 0,读取M[1],id = 1,读取M[0]
int message = 0;
message = shm[-id + 1]->message[shm[-id + 1]->front];
shm[-id + 1]->front = (shm[-id + 1]->front + 1) % MAX_NUM;
// printf("M%d -> front = %d\n", -id + 2, shm[-id + 1]->front);
return message;
}

//写入数据,id为0,表示A在操作,id为2,表示B在操作
int writeMessage(int id)
{
//随机生成一个范围在[1,4096]的message
int message = rand() % 4096 + 1;
//id为0,向M[0]写入,id为1,向M[1]写入
shm[id]->message[shm[id]->rear] = message;
shm[id]->rear = (shm[id]->rear + 1) % MAX_NUM;
// printf("M%d -> rear = %d\n", id + 1, shm[id]->rear);
return message;
}

//进程A的工作
void processA()
{
attach_shm();
//这里让A执行4次
for (int m = 0; m < 4; m++)
{
P(A_ENABLE);
//当前M1中元素个数
int ele1Number = (shm[0]->rear - shm[0]->front + MAX_NUM) % MAX_NUM;
//最多写入个数
int maxNum = MAX_NUM - ele1Number;
int randInt = rand() % 10 + 1;
//选一个小的数作为写入的信息数
int times = maxNum < randInt ? maxNum : randInt;
for (int i = 0; i < times; i++)
{
printf("A write message %d to M1\n", writeMessage(0));
}
//如果M2队列为空,也就是刚刚开始A向B,B还没向A发送过信息
//所以只在M2有消息的时候才读取
if (shm[1]->front != shm[1]->rear)
{
//当前M2中元素个数
int eleNumber = (shm[1]->rear - shm[1]->front + MAX_NUM) % MAX_NUM;
//随机读取[1, eleNumber]次
int times = rand() % eleNumber + 1;

for (int i = 0; i < times; i++)
{
printf("A read message %d from M2\n", readMessage(0));
}
}
V(B_ENABLE);
}
detach_shm();
}

//进程B的工作
void processB()
{
attach_shm();
//这里让B执行4次
for (int m = 0; m < 4; m++)
{
P(B_ENABLE);
//当前M1中元素个数
int eleNumber = (shm[0]->rear - shm[0]->front + MAX_NUM) % MAX_NUM;
int randInt = rand() % 10 + 1;
//选一个小的数作为读取的信息数
int times = eleNumber < randInt ? eleNumber : randInt;
for (int i = 0; i < times; i++)
{
printf("B read message %d from M1\n", readMessage(1));
}
//向M2写times个信息
for (int i = 0; i < times; i++)
{
printf("B write message %d to M2\n", writeMessage(1));
}
V(A_ENABLE);
}
detach_shm();
}

int main(int argc, char const *argv[])
{
//记录父进程pid
pid_t ppid = getpid();
//信号集名字,信号集中信号量的个数,信号量集合的权限
semid = semget((key_t)1234, NUM_MUX, IPC_CREAT | 0600); //创建信号量
if (semid == -1)
{
perror("semget");
}
// 初始化信号量
union semun s;
//初始时,允许A执行
s.value = 1;
semctl(semid, A_ENABLE, SETVAL, s);
//初始时,不允许B执行
s.value = 0;
semctl(semid, B_ENABLE, SETVAL, s);

//创建共享内存
shmid[0] = shmget((key_t)1234, sizeof(M[0]), 0666 | IPC_CREAT);
shmid[1] = shmget((key_t)5678, sizeof(M[1]), 0666 | IPC_CREAT);

if (shmid[0] == -1 || shmid[1] == -1)
{
fprintf(stderr, "shmget failed\n");
exit(EXIT_FAILURE);
}
attach_shm();
//初始化共享内存
for (int i = 0; i < 2; i++)
{
memset(shm[i]->message, 0, sizeof(shm[i]->message));
shm[i]->front = 0;
shm[i]->rear = 0;
}
//创建3个进程:1个父进程 + 1个A + 1个B
pid_t child_pid[2];
int i = 0;
for (i = 0; i < 2; i++)
{
child_pid[i] = fork();
//子进程
if (child_pid[i] == 0)
{
break;
}
}
//A
if (i == 0)
{
processA();
}
//B
else if (i == 1)
{
processB();
}
//父进程
if (getpid() == ppid)
{
//等待子进程结束
for (int i = 0; i < 2; i++)
{
waitpid(child_pid[i], NULL, 0);
}
detach_shm();
//删除共享内存
if (shmctl(shmid[0], IPC_RMID, 0) == -1 || shmctl(shmid[1], IPC_RMID, 0) == -1)
{
fprintf(stderr, "shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
}
return 0;
}

# 运行截图

M1M2运行截图1

M1M2运行截图2

M1M2运行截图3


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!