环境
Ubuntu18.04.3(desktop-amd64)4核70G、Linux 5.4.1(最新)、Deepin15.11(Ubuntu用起来不习惯)、draw.io、VSCode
Part1
目的
添加系统调用,扩展操作系统的功能。
①基本配置
①先下载源码,我是从主机上下载后传到虚拟机上的,放到/usr/src
文件夹中。下载链接
②解压,注意权限要切换成root。
tar -xavf linux-5.4.1.tar.xz
③依赖包,注意安装前更新一下apt,最好换一下源,碰到错误缺什么装什么。
apt-get install libncurses5-dev apt-get install libssl-dev apt-get install bison apt-get install flex apt-get install make
②添加系统调用号
vim arch/x86/entry/syscalls/syscall_64.tbl
③添加系统调用服务例程原型声明
vim include/linux/syscalls.h
④添加系统调用服务例程
vim kernel/sys.c
⑤配置内核
make menuconfig
图忘记截了,一般采用默认值就行,Save与Exit看到就选。
⑥编译内核
这里可以事先装一个ccache。(它保存了gcc的输出信息,等到下一次编译时有更新才会编译)
ccache包链接: https://pan.baidu.com/s/1laa5jO_ngp2HVgbMU__kNA 提取码: byjf
tar -xvf ccache-3.6.tar.xz cd ccache-3.6 ./configure -prefix=/var/ccache
然后编译内核。(-j4的4是内核数)
make -j4
10min左右就编完了,有了ccache下次编1min不到。
⑦编译模块
make modules
⑧安装内核等
安装模块:
make modules_install
安装内核:
make install
配置grub引导程序:
update-grub2
重启:
reboot
⑨测试
启动后,在/usr/src
添加一个测试文件。
demo1.c
用uname -a
查看内核版本,开始测试。
相关源码
find_get_pid
解释:根据进程号pid_t nr nr得到进程描述符struct pid ,并将进程描述符中的字段count+1。
struct pid *find_get_pid(pid_t nr) { struct pid *pid; rcu_read_lock(); pid = get_pid(find_vpid(nr)); rcu_read_unlock(); return pid; }
pid_task
解释:获取任务的任务描述符信息,此任务在进程pid的使用链表中,并且搜索的链表的起始元素的下标为参数type的值。
struct task_struct *pid_task(struct pid *pid, enum pid_type type) { struct task_struct *result = NULL; if (pid) { struct hlist_node *first; first = rcu_dereference_check(hlist_first_rcu(&pid->tasks[type]), lockdep_tasklist_lock_is_held()); if (first) result = hlist_entry(first, struct task_struct, pid_links[(type)]); } return result; }
copy_to_user
解释:从目标地址/用户空间将拷贝数据拷贝到源地址/内核空间,成功返回0,失败返回没有拷贝成功的数据字节数。
static __always_inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n) { if (likely(check_copy_size(from, n, true))) n = _copy_to_user(to, from, n); return n; }
task_nice
解释:用于获取当前task的nice值,并返回nice值。
static inline int task_nice(const struct task_struct *p) { return PRIO_TO_NICE((p)->static_prio); }
set_user_nice
解释:用于设置进程的nice值。
void set_user_nice(struct task_struct *p, long nice) { bool queued, running; int old_prio, delta; struct rq_flags rf; struct rq *rq; if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE) return; /* * We have to be careful, if called from sys_setpriority(), * the task might be in the middle of scheduling on another CPU. */ rq = task_rq_lock(p, &rf); update_rq_clock(rq); /* * The RT priorities are set via sched_setscheduler(), but we still * allow the ‘normal‘ nice value to be set - but as expected * it wont have any effect on scheduling until the task is * SCHED_DEADLINE, SCHED_FIFO or SCHED_RR: */ if (task_has_dl_policy(p) || task_has_rt_policy(p)) { p->static_prio = NICE_TO_PRIO(nice); goto out_unlock; } queued = task_on_rq_queued(p); running = task_current(rq, p); if (queued) dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK); if (running) put_prev_task(rq, p); p->static_prio = NICE_TO_PRIO(nice); set_load_weight(p, true); old_prio = p->prio; p->prio = effective_prio(p); delta = p->prio - old_prio; if (queued) { enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK); /* * If the task increased its priority or is running and * lowered its priority, then reschedule its CPU: */ if (delta < 0 || (delta > 0 && task_running(rq, p))) resched_curr(rq); } if (running) set_curr_task(rq, p); out_unlock: task_rq_unlock(rq, p, &rf); }
Part2
目的
①设计一个不带参数的模块,能列出系统中所有内核线程的程序名、PID、进程状态、进程优先级、父进程的PID
。
②设计一个带参数的模块,其参数为某个进程的PID号
,模块的功能是列出该进程的家族信息,包括父进程、兄弟进程和子进程的程序名、PID 号
及进程状态
。
不带参模块
第一部分是设计一个列出所有内核线程的程序名的不带参模块show_all_kernel_thread.c,这个需要通过利用内核进程的总链表实现,每个进程通过take_struct结构的next_task、prev_task成员加入总链表。
Makefile
模块编译
make
加载模块
insmod show_all_kernel_thread.ko
查看已加载模块
lsmod
查看某一模块是否加载可以用lsmod|grep 模块名
模块载入后,其中的init函数运行,将内核线程的一些信息打印到日志中。
dmesg查看系统日志。
New Terminal,ps -aux
显示所有内核线程。
可以看出是对应的。
卸载模块
rmmod 模块名
带参模块
第二部分是设计一个以某个进程的PID号为参数的,能列出这个进程的父进程、子进程和兄弟进程的模块 show_task_family。
#include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/moduleparam.h> #include <linux/pid.h> #include <linux/list.h> #include <linux/sched.h> MODULE_LICENSE("GPL"); static int pid; module_param(pid, int, 0644); static int __init show_task_family_init(void) { struct pid *ppid; struct task_struct *p; struct task_struct *pos; char *ptype[4] = {"[I]", "[P]", "[S]", "[C]"}; // 通过进程的PID号pid一步步找到进程的进程控制块p ppid = find_get_pid(pid); if (ppid == NULL){ printk("[ShowTaskFamily] Error, PID not exists.\n"); return -1; } p = pid_task(ppid, PIDTYPE_PID); // 格式化输出表头 printk("%-10s%-20s%-6s%-6s\n", "Type", "Name", "PID", "State");// Itself // 打印自身信息 printk("%-10s%-20s%-6d%-6d\n", ptype[0], p->comm, p->pid, p->state); // Parent // 打印父进程信息 printk("%-10s%-20s%-6d%-6d\n", ptype[1], p->real_parent->comm, p->real_parent->pid, p->real_parent->state); // Siblings // 遍历父进程的子,即我的兄弟进程,输出信息 // “我”同样是父进程的子进程,所以当二者进程PID号一致时,跳过不输出 list_for_each_entry(pos, &(p->real_parent->children), sibling){ if (pos->pid == pid) continue; printk("%-10s%-20s%-6d%-6d\n", ptype[2], pos->comm, pos->pid, pos->state); } // Children // 遍历”我“的子进程,输出信息 list_for_each_entry(pos, &(p->children), sibling){ printk("%-10s%-20s%-6d%-6d\n", ptype[3], pos->comm, pos->pid, pos->state); } return 0; } static void __exit show_task_family_exit(void){ printk("[ShowTaskFamily] Module Uninstalled.\n"); } module_init(show_task_family_init); module_exit(show_task_family_exit);
Makefile
obj-m := show_task_family.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean
以相同的方式载入,这里注意需要输入一个pid参数。
查看进程树。
pstree -p
以pid为1293为例。
其中I
表示自己进程,P
表示父进程,C
表示兄弟进程,S
表示子进程。
相关源码
for_each_process
参考自Linux-4.1.2/include/linux/sched/signal.h
#define for_each_process(p) for (p = &init_task ; (p = next_task(p)) != &init_task ; )
通过它可以扫描整个进程链表。
list_for_each_entry
参考自Linux-4.1.2/include/linux/list.h
/* struct task_struct *pos; list_for_each_entry(pos, &pos->children, sibling); */ #define list_for_each_entry(pos, head, member) for (pos = __container_of((head)->next, pos, member); &pos->member != (head); pos = __container_of(pos->member.next, pos, member))
其中,container_of()来自Linux-4.1.2/include/linux/kernel.h,计算返回包含ptr指向的成员member所在的type类型数据结构的指针。
#define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) * __mptr = (ptr); (type *)((char *)__mptr - offsetof(type, member)); })
list_for_each_entry将一个叫做list_head的数据结构放在结构体中,就可以使结构体具有链表的功能。图片来源:https://my.oschina.net/u/3857782/blog/1849617/
list_head一方面通过自身的prev和next指针,构成一个链表,这部分是粘钩的钩子,另一方面,通过寄生在其他的结构体中,就像粘钩带粘性的那一端,将结构体粘住。这样作为宿主的结构体也在链表中了。
part3
目的
①实现一个模拟shell
编写三个不同的程序cmd1.c、cmd2.c、cmd3.c,每个程序功能自定,分别编译成可执行文件cmd1、cmd2、cmd3。
然后再编写一个程序,模拟shell程序的功能:能根据用户输入的字符串(表示相应的命令名),去为相应的命令创建子进程并让它去执行相应的程序,而父进程则等待子进程的结束,然后再等待接收下一条命令。
如果接收到的命令为exit,则父进程结束,退出模拟shell,如果接收到无效命令,则显示“Command not found”,继续等待下一条命令。
②实现一个管道通信程序
由父进程创建一个管道,然后再创建三个子进程,并由这三个子进程用管道与父进程之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。
通信的具体内容可根据自己的需要随意设计,要求能够实验阻塞型读写过程的各种情况,测试管道的默认大小,并要求利用Posix信号量机制实现进程间对管道的互斥访问。
运行程序,观察各种情况下,进程实际读写的字节数以及进程阻塞唤醒情况。
③利用linux的消息队列通信机制实现两个线程间的通信
编写程序创建三个线程:sender1线程、sender2线程、receiver线程,三个线程的功能描述如下:
a、sender1:运行函数sender1(),它创建一个消息队列,然后等待用户通过终端输入一串字符,并将这串字符通过消息队列发送给receiver线程;可循环发送多个消息,直到用户输入“exit”为止,表示它不在发送消息,最后向receiver线程发送消息“end1”,并且等待receiver的应答,等到应答消息后,将接收到的应答消息显示在终端屏幕上,结束线程的运行。
b、sender2:运行函数sender2(),共享sender1创建的消息队列,等待用户通过终端输入一串字符,并且将这串字符通过消息队列发送给receiver线程;
可循环发送多个消息,直到用户输入“exit”为止,表示它不再发送消息,最后向receiver线程发送消息“end2”,并且等待receiver的应答,等到应答消息后,将接收到的信息显示在终端屏幕上,结束线程的运行。
c、receiver:运行函数receiver(),它通过消息队列接收来自sender1和sender2两个线程的消息,将消息显示在终端屏幕上,当收到的内容为“end1”的消息时,就向sender1发送一个应答消息“over1”;
当收到内容为“end2”的消息时,就向sender2发送一个应答消息“over2”;
消息接收完成后删除消息队列,结束线程的运行。选择合适的信号量机制实现三个线程之间的同步与互斥。
④利用Linux的共享内存通信机制实现两个进程间的通信
编写程序sender,它创建一个共享内存,然后等待用户通过终端输入一串字符,并将这串字符通过共享内存发送给receiver,最后,它等待receiver的应答,收到应答消息后,它接收到的应答消息显示在终端屏幕上,删除共享内存,结束程序运行。
编写receiver程序,它通过共享内存接收来自sender的消息,将消息显示在终端屏幕上,然后再通过该共享内存向sender发送一个应答消息”over”,结束程序的运行。使用合适的信号量机制实现两个进程对共享内存的互斥及同步使用。
模拟Shell
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #define CMD_COLLECTION_LEN 4 //命令数组的长度(有哪几个命令) //command index #define INVALID_COMMAND -1 //无效命令返回-1 #define EXIT 0 #define CMD_1 1 #define CMD_2 2 #define CMD_3 3 char *cmdStr [CMD_COLLECTION_LEN ]= {"exit","cmd1","cmd2","cmd3"}; //对比所有命令参数,如果有一样的,就返回对应数字,用于后面执行 int getCmdIndex(char *cmd) { int i; //遍历数组寻找命令 for(i=0;i<CMD_COLLECTION_LEN;i++){ if (strcmp(cmd,cmdStr[i])==0){ return i; } } return INVALID_COMMAND; } /* 创建子进程,这里使用了execl,后面的l表示list,即参数列表。 第一参数为path(要执行的文件路径),最后一个参数必须是NULL, 中间的为要传送的参数 */ void myFork(int cmdIndex) { pid_t pid; //即 int if((pid = fork())<0){ printf("Fork subprocess failure!\n"); exit(0); } else if (pid == 0){ int execl_status = -1; //初始化为-1 printf("\nSubprocess working...\n"); switch(cmdIndex){ case CMD_1: execl_status = execl("./cmd1","cmd1",NULL); //函数执行不成功返回-1 break; case CMD_2: execl_status = execl("./cmd2","cmd2",NULL); break; case CMD_3: execl_status = execl("./cmd3","cmd3",NULL); break; default: printf("ERROR! Command NOT FOUND!\n"); break; } // 返回值为-1则表明出错 if(execl_status<0){ printf("Executing execl() ERROR!\n"); exit(0); } else{ printf("Process complete!\n"); exit(0); } } else{ return; } } //运行cmd void runCMD(int cmdIndex) { switch(cmdIndex){ case INVALID_COMMAND: printf("Command Not Found!\n"); break; case EXIT: exit(0); break; default: myFork(cmdIndex); break; } } int main() { pid_t pid; char cmdStr[30]; //命令数组(最长30) int cmdIndex; //用于显示运行哪个数据 while(1){ printf("\nPlease input command\n>>:"); scanf("%s",cmdStr); cmdIndex = getCmdIndex(cmdStr); runCMD(cmdIndex); wait(0); } }
管道通信
思路:
无名管道用于具有的亲缘关系的进程,父子兄弟进程。
在阻塞方式下,若设备不可读写,则该进程休眠,释放CPU资源;若设备文件可读写,则对设备文件进行读写。
在非阻塞方式下,若设备不可读写,进程放弃读写,继续向下执行;若设备文件可读写,则对设备文件进行读写。
简而言之: 非阻塞读写要加入判断语句(在管道满无法继续写入时返回-EAGAIN,作为循环终止条件);
而阻塞型读写在指定读写量后自行终止。
阻塞方式,使用‘Posix信号量‘机制辅以实现对管道的读写使用;非阻塞方式使用‘循环+判断‘机制实现读写的终止。
#include <errno.h> #include <fcntl.h> #include <semaphore.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #define BUF_MAX_SIZE 8192 #define CHECK(x) do { if (!(x)) { fprintf(stderr, "%s:%d: ", __func__, __LINE__); perror(#x); exit(-1); } } while (0) /** 无名管道用于具有的亲缘关系的进程,父子兄弟进程。 在阻塞方式下,若设备不可读写,则该进程休眠,释放CPU资源;若设备文件可读写,则对设备文件进行读写。 在非阻塞方式下,若设备不可读写,进程放弃读写,继续向下执行;若设备文件可读写,则对设备文件进行读写。 简而言之: 非阻塞读写要加入判断语句(在管道满无法继续写入时返回-EAGAIN,作为循环终止条件); 而阻塞型读写在指定读写量后自行终止。 阻塞方式,使用‘Posix信号量‘机制辅以实现对管道的读写使用;非阻塞方式使用‘循环+判断‘机制实现读写的终止 */ int main(int argc, char **argv) { int pipefd[2], pid, i = 0; //0读,1写 int flag = 0; //文件状态标志 ssize_t n; // aka. long int char buf[BUF_MAX_SIZE];//用作write的缓冲区,保存写入的字符 char str[BUF_MAX_SIZE];//用作read的缓冲区,保存读取的字符 //创建有名信号量,若不存在则创建,若存在则直接打开,默认值为0 sem_t *write_mutex;//限制了父进程先读取数据,然后子进程二、三写入数据 sem_t *read_mutex1;//限制了子进程2 写入数据 2,然后父进程读取数据 2 sem_t *read_mutex2;//限制了子进程3 写入数据 3,然后父进程读取数据 3 write_mutex = sem_open("pipe_test_wm", O_CREAT | O_RDWR, 0666, 0);//创建|读写方式打开,权限位,初始值=0(为了确保子进程一拿到信号量,防止子进程二、三抢占) read_mutex1 = sem_open("pipe_test_rm_1", O_CREAT | O_RDWR, 0666, 0); read_mutex2 = sem_open("pipe_test_rm_2", O_CREAT | O_RDWR, 0666, 0); //字符置换函数,内存空间初始化0 memset(buf, 0, BUF_MAX_SIZE); memset(str, 0, BUF_MAX_SIZE); // 创建匿名管道并检查操作是否成功 CHECK(pipe(pipefd) >= 0); // 创建第一个子进程并检查操作是否成功 CHECK((pid = fork()) >= 0); // 第一个子进程,利用非阻塞写测试管道大小 if (pid == 0) { int count = 0; close(pipefd[0]);//close reading //F_GETFL获取文件状态标志 int flags = fcntl(pipefd[1], F_GETFL);//FCNTL()控制已打开文件的的各种属性 // 新建的匿名管道默认是阻塞写,通过`fcntl`设置成非阻塞写,在管道满无法继续写入时返回-EAGAIN,作为循环终止条件 fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK);//F_SETFL设置文件状态标志,非阻塞写 // 写入管道 printf("\n>>Child1 (非阻塞写):\n"); while (!flag) { //从缓冲器中向pipefd[1]的偏移量后写入buf中的BUF_MAX_SIZE字节,直到无法继续写入 //成功则返回写入的字节数,若出错则返回-1 n = write(pipefd[1], buf, BUF_MAX_SIZE); if (n == -1) { flag = 1; } else { count++; printf("write %ld B to pipe\n", n); } } printf("\nDefault pipe size = %d KB\n", (count * BUF_MAX_SIZE) / 1024);//打印8times,64kb. exit(0); } // 创建第二个子进程并检查操作是否成功 CHECK((pid = fork()) >= 0); if (pid == 0) { sem_wait(write_mutex);//父进程先读子进程一,子进程二再写 close(pipefd[0]);//关闭管道读端 n = write(pipefd[1], "This is the second child.\n", 28);//子进程二、三均为阻塞性写 printf("\n>>Child 2 (阻塞写):\nwrite %ld B to pipe\n", n); sem_post(write_mutex);//子进程二写完成 sem_post(read_mutex1);//父进程读进程二 exit(0); } // 创建第三个子进程并检查操作是否成功 CHECK((pid = fork()) >= 0); if (pid == 0) { sem_wait(write_mutex);//请求管道write close(pipefd[0]); n = write(pipefd[1], "This is the third child.\n", 18); printf("\n>>Child 3 (阻塞写):\nwrite %ld B to pipe\n", n); sem_post(write_mutex);//子进程三写完成 sem_post(read_mutex2);//父进程读进程三 exit(0); } //父进程必须接收到子进程结束之后返回的 0,才能继续运行,否则阻塞。 //读取子进程一写入的数据,否则子进程二、三无法继续写入. //读空管道后结束循环,释放信号量,子进程二、三继续运行。 wait(0); close(pipefd[1]);//关闭管道wirte端 int flags = fcntl(pipefd[0], F_GETFL);//F_GETFL获取文件状态标志;出错返回-1 // 设置非阻塞性读,直到不能再读入,作为循环结束标志 fcntl(pipefd[0], F_SETFL, flags | O_NONBLOCK); printf("\n>>Father (非阻塞读):\n"); while (!flag) { n = read(pipefd[0], str, BUF_MAX_SIZE); if (n == -1) { flag = 1; } else { printf("read %ld B from pipe\n", n); } } fcntl(pipefd[0], F_SETFL, flags | ~O_NONBLOCK);// 设置阻塞性读,作为循环结束标志 printf("\n>>Father (阻塞读):\n"); fcntl(pipefd[1], F_SETFL, flags | ~O_NONBLOCK);// 设置阻塞性写 sem_post(write_mutex);//子进程二、三继续写入 // 等待子进程二、三写入完毕,输出子进程二三写入的数据 sem_wait(read_mutex1); sem_wait(read_mutex2); n = read(pipefd[0], str, BUF_MAX_SIZE);//返回实际读取的数据大小 printf("\n>>Father:\nread %ld B from pipe (actual size)\n\nThe contents are:\n", n); for (i = 0; i < n; i++) { printf("%c", str[i]); } printf("\n\n注:以上读写字节数均为‘实际读写字节数‘\n"); //关闭信号量 sem_close(write_mutex); sem_close(read_mutex1); sem_close(read_mutex2); //系统中删除有名信号量 sem_unlink("pipe_test_wm"); sem_unlink("pipe_test_rm_1"); sem_unlink("pipe_test_rm_2"); return 0; }
消息队列
思路:
本次实验采用了3个semaphore信号量,但是我认为完全可以采用flag机制更好。
(下面的共享内存是用的flag机制,这样做更加便于理解,直接设置3个信号量)
过一段时间自己写的是啥都忘了...重新看一遍会耗费时间
/* 本次实验采用了3个semaphore信号量,但是我认为完全可以采用flag机制 (可参考实验3-4对flag机制使用) 这样做更加便于理解,直接设置3个信号量 过一段时间自己写的是啥都忘了...重新看一遍会耗费时间 */ #include <pthread.h> #include <semaphore.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/ipc.h> #include <sys/msg.h> #define TRUE 1 #define BUF_SIZE 128 // maxium message size #define KEY_NUM 0 // assign key number //int f = 0; // count finished senders (f==2: all threads abort) ; aka. ‘flag‘, which has been utilized in Receiver int sender_id = 0; // [Improvement] if Sender1 sent ‘end2‘, Sender2 would not be aborted int msgid; // message id // message buffer struct; in order to simulate msgbuf in Linux, we added attribute ‘mtype‘, actually it is unnecessary in this project typedef struct msgbuf { long int mtype; // unsigned long type,>0, receive function used it to confirm message type char mtext[BUF_SIZE]; // 128,message content }msgbuf; sem_t full; // whether message queue is full (whether available for receiver) sem_t empty; // whether message queue is empty (whether available for sender) sem_t mutex; // whether message queue available key_t key; // only match one queue // Receiver void * Receiver(void *arg) { msgbuf msg; msg.mtype = 1; // define mtype as type "1"; it is unnecessary, could be eliminated int flag=0; // judge if it reached ends while(TRUE) { sem_wait(&full); // wait for message queue full (available for Receiver) sem_wait(&mutex); // wait for message queue available msgrcv(msgid,&msg,sizeof(msgbuf),1,0); // mtype=1, 0: ignore (to control if no appointed mtype mathched); Function Prototype: int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg) printf("\n\n>>Receiver: %s\n",msg.mtext); if(strcmp(msg.mtext,"end1") == 0 && sender_id == 1) // [Improvement] { msg.mtype = 2; // change mtype to 2, abort Sender1 strncpy(msg.mtext,"over1",BUF_SIZE); // aka. msg->mtext = over1, yet C does not confirm this writing style msgsnd(msgid,&msg,sizeof(msgbuf),0); // fourth parameter: 0->ignore zone bit sem_post(&mutex); flag++; // Sender1 ended } else if(strcmp(msg.mtext,"end2") == 0 && sender_id == 2) { msg.mtype = 3; // change mtype to 3, abort Sender2 strncpy(msg.mtext,"over2",BUF_SIZE); msgsnd(msgid,&msg,sizeof(msgbuf),0); sem_post(&mutex); flag++; // Sender2 ended } else{ sem_post(&empty); // message queue empty (Receiver has received message) sem_post(&mutex); // message queue available } // exit if(flag == 2){ sleep(1); sem_destroy(&full); sem_destroy(&empty); sem_destroy(&mutex); exit(EXIT_SUCCESS); } } } // Sender1 void * Sender1(void *arg) { char str[BUF_SIZE]; int flag = 0; // [Improvement] after ‘exit‘,flag=1,which means no longer send message msgbuf msg; while (TRUE) { msg.mtype = 1; sem_wait(&empty); // wait for message queue empty (available for Sender) sem_wait(&mutex); // wait for message queue available if(flag == 0){ printf("\n\n>>Sender1\nPlease input the message you want to send:\n"); scanf("%s", str); while(getchar()!=‘\n‘); } else{ while(1){ printf("\n\n>>Sender1\nFrom now on, you could not enter message but ‘end1‘\n"); scanf("%s", str); while(getchar()!=‘\n‘); if( strcmp(str,"end1") == 0 ) break; } } if( strcmp(str,"end1") == 0 && flag == 1) { sender_id = 1; // being utilized in the improvement part strncpy(msg.mtext,str,BUF_SIZE); msgsnd(msgid,&msg,sizeof(msgbuf),0); sem_post(&full); sem_post(&mutex); sleep(0.5); sem_wait(&mutex); msgrcv(msgid,&msg,sizeof(msgbuf),2,0); printf("\nFrom Receiver: %s\n", msg.mtext); sem_post(&empty); sem_post(&mutex); break; } else if(strcmp(str,"exit") == 0 && flag == 0){ printf("Sender1 exit\n"); flag = 1; // no longer send message sem_post(&empty); // Sender2 is available sem_post(&mutex); sleep(1); // Switch to Sender2 } else if(strcmp(str,"end1") != 0 && flag == 0){ sender_id = 1; // being utilized in the improvement part strncpy(msg.mtext,str,BUF_SIZE); msgsnd(msgid,&msg,sizeof(msgbuf),0); sem_post(&full); // message queue full (message has been sent to the queue) sem_post(&mutex); // message queue available } else{ printf("\nPlease input ‘exit‘ before entering ‘end1‘ or ‘end2‘\n"); sem_post(&empty); sem_post(&mutex); } } } // Sender2 void * Sender2(void *arg) { char str[BUF_SIZE]; int flag = 0; msgbuf msg; while (TRUE) { msg.mtype = 1; sem_wait(&empty); // wait for message queue empty (available for Sender) sem_wait(&mutex); // wait for message queue available if(flag == 0){ printf("\n\n>>Sender2\nPlease input the message you want to send:\n"); scanf("%s", str); while(getchar()!=‘\n‘); } else{ while(1){ printf("\n\n>>Sender2\nFrom now on, you could not enter message but ‘end2‘\n"); scanf("%s", str); while(getchar()!=‘\n‘); if( strcmp(str,"end2") == 0 ) break; } } if( strcmp(str,"end2") == 0 && flag == 1) { sender_id = 2; // being utilized in the improvement part strncpy(msg.mtext,str,BUF_SIZE); msgsnd(msgid,&msg,sizeof(msgbuf),0); sem_post(&full); sem_post(&mutex); sleep(0.5); sem_wait(&mutex); msgrcv(msgid,&msg,sizeof(msgbuf),3,0); printf("\nFrom Receiver: %s\n", msg.mtext); sem_post(&empty); sem_post(&mutex); break; } else if(strcmp(str,"exit") == 0 && flag == 0){ printf("Sender2 exit\n"); flag = 1; // no longer send message sem_post(&empty); // Sender1 is available sem_post(&mutex); sleep(1); // Switch to Sender1 } else if(strcmp(str,"end2") != 0 && flag == 0){ sender_id = 2; // being utilized in the improvement part strncpy(msg.mtext,str,BUF_SIZE); msgsnd(msgid,&msg,sizeof(msgbuf),0); sem_post(&full); // message queue full (message has been sent to the queue) sem_post(&mutex); // message queue available } else{ printf("\nPlease input ‘exit‘ before entering ‘end1‘ or ‘end2‘\n"); sem_post(&empty); sem_post(&mutex); } } } int main() { pthread_t sender_pid; pthread_t receiver_pid; sem_init(&full,0,0); // first 0: multi-thread synchronization; second 0: initial value sem_init(&empty,0,1); sem_init(&mutex,0,1); key = KEY_NUM; // assign ‘key‘ value // first parameter: match the key of message queue; second parameter: user read/ write/ create if not exist if((msgid = msgget(key, S_IRUSR | S_IWUSR | IPC_CREAT)) == -1) { printf("Create Message Queue Error!\n"); exit(EXIT_FAILURE); // abnormal exit } else{ printf("Create Message Queue Complete!\n\n"); } pthread_create(&sender_pid,NULL,Sender1,NULL); // second parameter: attributes (prio/size...), default is NULL; pthread_create(&sender_pid,NULL,Sender2,NULL); // third parameter: execute function (indicator function) pthread_create(&receiver_pid,NULL,Receiver,NULL); // fourth parameter: thread execution parameter pthread_join(sender_pid,NULL); // waiting for the thread end pthread_join(receiver_pid,NULL); msgctl(msgid,IPC_RMID,NULL); printf("\n\nMain Function End...\n"); return 0; }
共享内存
我认为信号量机制的实现不必要引用系统提供的semaphore,
可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制;
这样做有比直接使用semaphore更为改进之处:
1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观;
2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试
(虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件)
Sender
/* 经过实验三中第三个小实验的经历,我认为信号量机制的实现不必要引用系统提供的semaphore, 可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制; 这样做有比直接使用semaphore更为改进之处: 1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观; 2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试 (虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件) */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/shm.h> #include <string.h> void *pAddr; int shmId; //共享内存 struct Msg{ int flag; // 0:Read; 1:Write; 2:Cease char content[32]; }; // Sender void main(){ key_t key = ftok(".",2); //key_t key = 123456; //也可以直接给出key值,只要保证和Sender中的一样即可 shmId = shmget(key,128,IPC_CREAT | IPC_EXCL | 0666); pAddr = shmat(shmId,0,0); if(*(int *)pAddr == -1){ printf("shmat error!\n"); exit(0); } struct Msg * msg = (struct Msg *)pAddr; memset(pAddr,0,128); msg->flag = 1; //只写 while(1){ if(msg->flag == 1){ printf("\nFrom Receiver:\n"); printf("%s",msg->content); //这里改为gets()等函数可以实现语句的输入,并在后续传输整个句子,不局限于一个单词 printf("\n\n>>Sender\nPlease input contents:\n"); scanf("%s", msg->content); msg->flag = 0; //只读 } else if(msg->flag == 2){ //strcmp(msg->content,"over") == 0 printf("\n>>Sender\nFrom Receiver: over!\n"); shmdt(pAddr); shmctl(shmId,IPC_RMID,0); exit(0); } } }
Receiver
/* 经过实验三中第三个小实验的经历,我认为信号量机制的实现不必要引用系统提供的semaphore, 可以直接在共享内存空间中引入flag变量,以实现共享内存控制机制; 这样做有比直接使用semaphore更为改进之处: 1. 系统提供的信号量机制只能实现 True or False,但是flag变量可以实现0,1,2三种变量,控制更为直观; 2. 程序可以写在多个文件中,不需要在一串代码中完成,便于更加直观的调试 (虽然不可否认使用semaphore机制也可以实现 --- 增加判断语句判定终止条件) */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/shm.h> #include <string.h> void *pAddr; //新建指针,用以指向当前进程地址 int shmId; //共享内存ID struct Msg { int flag; // 0:Read; 1:Write; 2:Cease char content[32]; }; // Server void main() { key_t key = ftok(".",2); //把当前文件路径名和一个整数标识符转换成IPC的key值,以保证和clint中的共享内存一致;第二个参数:计划代号 //key_t key = 123456; //也可以直接给出key值,只要保证和Sender中的一样即可 shmId = shmget(key,0,0); //如果没有该块共享内存,则创建、并返回共享内存ID;若已有该块共享内存,则返回-1 pAddr = shmat(shmId,0,0); //共享内存连接到当前进程的地址空间 if(*(int *)pAddr == -1) { printf("shmat error!\n"); exit(0); } struct Msg *msg = (struct Msg *)pAddr; while(1) { if(msg->flag == 0) //只读 { printf("\n\n>>Receiver\n\nFrom sender: %s\n", msg->content); printf("Please response (you may enter ‘over‘ to abort the process):\n"); scanf("%s", msg->content); if(strcmp(msg->content,"over") == 0){ msg->flag = 2; //通知Sender共享内存结束 exit(0); } else{ msg->flag = 1; //进程未结束,继续从Sender获取讯息 } } } }
参考链接
https://blog.csdn.net/babybabyup/article/details/79720082
https://blog.csdn.net/zyf2333/article/details/80043152
https://www.linuxidc.com/Linux/2016-04/129955.htm
https://www.jianshu.com/p/60d2b4f86159
http://www.voidcn.com/article/p-tmnvznnb-bmp.html
https://blog.csdn.net/xiao_jj_jj/article/details/82755954
https://blog.csdn.net/zxhio/article/details/80312316
https://blog.csdn.net/w13635739860/article/details/99288899
https://zhuanlan.zhihu.com/p/87420486
部分链接的内容存在bug,仅参考的一部分。
原文地址:https://www.cnblogs.com/fangxiaoqi/p/11777380.html