本篇博客参考了dzcgiegie的好文啦:系统调用与进程通信
3.1 Linux 之系统调用
任务描述:通过对作业二形成的系统新内核进行相应的修改,加入自己的系 统调用,并且在应用层编写一应用程序对其进行调用,产生相应的输出给予验证。
作为一个懒人,那肯定要省时间的方法啊,谁会再去等至少一个小时的内核编译时间啊
1.查询 sys_call_table 的地址
1
| sudo cat /proc/kallsyms | grep sys_call_table
|
找到sys_call_table 的地址为0xffffffff91a013a0
2.查询可用的系统调用号
1
| cat /usr/include/asm-generic/unistd.h
|
这里我是这个路径,其他人好像不是,
1
| sudo find / -name unistd*.h
|
反正找到这个unistd
文件就行,然后看一下哪个号没被占用
找到415号是可用的系统调用号
3.创建lnm.c文件
在文件内定义系统调用,其中使用到的 sys_call_table 地址和可用系统调用号为上面找到的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include<linux/module.h> MODULE_INFO(intree,"Y"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("LNM"); #define SYS_CALL_TABLE_ADDRESS 0xffffffff91a013a0 #define NUM 415 static int __init hello_init(void) { printk(KERN_INFO "Welcome ! 20192131031+liangnuoming\n"); return 0; }
static void __exit hello_exit(void) { printk(KERN_INFO "Bye ! 20192131031+liangnuoming\n"); } module_init(hello_init); module_exit(hello_exit);
|
4.创建Makefile调用文件
先运行下面的命令找到系统当前的内核,然后去/usr/src/目录找对应的文件夹,记住这个文件夹的路径/usr/src/linux-headers-5.4.0-88-generic
然后创建Makefile文件
1 2 3 4 5 6 7
| obj-m:=lnm.o CURRENT_PATH:=$(shell pwd) LINUX_KERNEL_PATH:=/usr/src/linux-headers-5.4.0-88-generic all: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules clean: make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
|
5.安装内核模块
执行sudo make
可以通过ls | grep ‘lnm.*’
查看执行是否成功
使用如下命令插入模块:
使用如下命令检查插入是否成功:
可以看到lnm的模块,插入成功。
6.创建test.c调用文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #include<stdio.h> #include<stdlib.h> #include<linux/kernel.h> #include<sys/syscall.h> #include<unistd.h> int main() {
unsigned long x = 0; x = syscall(415); printf("调用测试成功"); return 0; }
|
使用 gcc test.c
和 ./a.out
命名查看运行结果:
通过 dmesg
命令查看系统调用的结果:
说明添加系统调用成功
3.2 IPC
代码实践 1:
本人在学者网的课程主页中上传了相关 Slides(教学资源里名 为“Linux-OS.zip“的压缩包),其第 12、13 章对上述四种 IPC 方式的使用过程和 示范代码进行了详细的解释。所以这里就请大家先根据上述压缩包里的教程,把 四种 IPC 方式的代码在自己的虚拟机里正确的运行起来并仔细理解。因为这些隶 属于应用层代码,所以难度较低。
若是执行gcc以后报错,可以用man + 函数名 命令解决该头文件缺失的问题
例如man fork
管道 - 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 33 34
| #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include <sys/wait.h> int main() { int p1,fd[2]; char outpipe[50]; char inpipe[50]; 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); } }
|
执行sudo gcc pipe.c -o pipe
和./pipe
信号 - Signal
创建 signal.c 文件,并在其中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #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!20192131031 + liangnuoming\n"); } printf("OK\n"); exit(0);
}
|
执行sudo gcc signal.c -o signal
和./signal
消息传递 - Message Passing Interface
创建 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
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/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]; msgid = msgget(12,0666 | IPC_CREAT); while (1) { puts("Enter some text:"); fgets(buffer, BUFSIZ, stdin); msg.i++ ; printf("i=%d\n",msg.i); msg.my_msg_type = 3; strcpy(msg.some_text, buffer); msgsnd(msgid, &msg, MAXMSG, 0); if (strncmp(msg.some_text, "end", 3) == 0) break; } exit(0); }
|
创建 rcvfile.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
| #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include <sys/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; 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); if(strncmp(msg.some_text,"end",3)==0) break; } msgctl(msgid,IPC_RMID,0); exit(0);
}
|
执行sudo gcc sndfile.c -o sndfile
和sudo gcc rcvfile.c -o rcvfile
执行./sndfile
执行./rcvfile
共享内存 - Shared Memory
创建 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
| #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; char buffer[BUFSIZ]; shmid = shmget(1234, BUFSIZ, 0666 | IPC_CREAT); viraddr = (char*)shmat(shmid, 0, 0); while (1) { puts("Enter some text:"); fgets(buffer, BUFSIZ, stdin); strcat(viraddr, buffer); if (strncmp(buffer, "end", 3) == 0) break; } shmdt(viraddr); exit(0); }
|
创建 rcvshm.c 文件,并在其中添加以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #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 :%s", viraddr); shmdt(viraddr); shmctl(shmid, IPC_RMID, 0); exit(0); }
|
执行sudo gcc sndshm.c -o sndshm
和sudo gcc rcvshm.c -o rcvshm
执行./sndshm
和./rcvshm
代码实践 2:
请对上述 IPC 方式的第四种——共享内存进行改写,实现以下 场景。
- (1)创建两块共享内存 M1 和 M2;用数据结构“环形队列/数组”来抽象 M1 和 M2,也即,每个共享内存中的存储形式为一循环队列或数组,容量为 4096(可 以容纳 4096 个消息);
- (2)发送者进程,名为 A,向 M1 中“生产”常规信息(可以是字符串),从 M2 中“消费”控制信息;接收者进程,名为 B,向 M2“生产”控制信息,从 M1 中 “消费”常规信息;这里可以理解为:M1 为 A 向 B 发送数据的缓冲(Data Buffer), M2 则为 B 向 A 发送应答(确认收到的应答)的缓冲(Response Buffer);
- (3)初始状态下,M1 和 M2 皆为空;A 首先主动向 B 发送常规信息,数量 为[1-10]中的随机数目(但是每次发送的数据量肯定不能超过 M1 中的空闲槽数 量);发送之后,A 再向 B 传送信号(Signal),要其消费 M1;
- (4)B 收到 A 发送来的信号后,从 M1 中消费信息,数量为[1-10]中的随机 数量(但是肯定不能超过 M1 中的当前存在消息数目);B 向 M2 中生产应答信 息,数目为其最近消费 M1 的消息个数,即:B 一次消费的常规信息数,要等于 生产的应答信息数;之后,B 向 A 发送信号,让其接收应答信息;
- (5)A 收到信号后,消费应答信息,并继续向 M1 中生产常规信息。
本题就是一个pv操作,A读写的时候B不能读写,B读写的时候A不能,所以设置两个信号量就能控制这个问题,达到轮流操作的效果
代码参考了系统调用与进程通信与生产者消费者问题 多进程共享内存 - LightningStar,并改成了随时间变化的随机数,使每次运行结果都为伪随机的
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
| #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; };
struct Resource M[2];
union semun { int value; };
enum MUTEX { A_ENABLE, B_ENABLE, NUM_MUX };
int semid;
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); } }
void P(short unsigned int num) { struct sembuf sb = { num, -1, 0 }; semop(semid, &sb, 1); }
void V(short unsigned int num) { struct sembuf sb = { num, 1, 0 }; semop(semid, &sb, 1); }
int readMessage(int id) { int message = 0; message = shm[-id + 1]->message[shm[-id + 1]->front]; shm[-id + 1]->front = (shm[-id + 1]->front + 1) % MAX_NUM; return message; }
int writeMessage(int id) { sleep(1.0); srand((unsigned)time(NULL)); int message = rand() % 4096 + 1; shm[id]->message[shm[id]->rear] = message; shm[id]->rear = (shm[id]->rear + 1) % MAX_NUM; return message; }
void processA() { attach_shm(); for (int m = 0; m < 4; m++) { P(A_ENABLE); int ele1Number = (shm[0]->rear - shm[0]->front + MAX_NUM) % MAX_NUM; int maxNum = MAX_NUM - ele1Number; sleep(1.0); srand((unsigned)time(NULL)); int randInt = rand() % 10 + 1; int times = maxNum < randInt ? maxNum : randInt; for (int i = 0; i < times; i++) { printf("A write message to M1 : %d \n", writeMessage(0)); } if (shm[1]->front != shm[1]->rear) { int eleNumber = (shm[1]->rear - shm[1]->front + MAX_NUM) % MAX_NUM; sleep(1.0); srand((unsigned)time(NULL)); int times = rand() % eleNumber + 1;
for (int i = 0; i < times; i++) { printf("A read message from M2 : %d \n", readMessage(0)); } } V(B_ENABLE); } detach_shm(); }
void processB() { attach_shm(); for (int m = 0; m < 4; m++) { P(B_ENABLE); sleep(1.0); srand((unsigned)time(NULL)); 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 from M1 : %d \n", readMessage(1)); } for (int i = 0; i < times; i++) { printf("B write message to M2 : %d \n", writeMessage(1)); } V(A_ENABLE); } detach_shm(); }
int main(int argc, char const *argv[]) { extern int flag; pid_t ppid = getpid(); semid = semget((key_t)1234, NUM_MUX, IPC_CREAT | 0600); if (semid == -1) { perror("semget"); } union semun s; s.value = 1; semctl(semid, A_ENABLE, SETVAL, s); 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; } pid_t child_pid[2]; int i = 0; for (i = 0; i < 2; i++) { child_pid[i] = fork(); if (child_pid[i] == 0) { break; } } if (i == 0) { processA(); } 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; }
|
3.3 心得和体会
通过3.1的内核模块学会了一些关于内核模块,KO文件,Makefile的知识,懂了怎么插入和自定义内核模块,当然了,掌握的还是很浅薄,不过万丈高楼平地起,也算是有了一点基础。不得不说Linux的可玩性比Windows和macOS好太多了,不过也要有能力才能玩起来。
上学期学了操作系统,一直没什么实践的机会,这次写了一下pv操作的逻辑,学会了几个os相关的api,虽然也是一知半解的状态,不过感觉对底层的东西敬意又多了不少。