进程同步实验 ipcs -*ipcs -mlinux中可用命令ipcs -m 观察共享内存 情况
key 共享内存关键值
shmid 共享内存标识
owner 共享内存所由者
perm 共享内存使用权限
byte 共享内存字节数
nattch 共享内存使用计数
status 共享内存状态
ipcs -slinux中可用命令ipcs -s 观察信号量数组 的情况
semid 信号量的标识号
nsems 信号量的个数
ipcs -qlinux中可用命令ipcs -q 观察消息队列 的情况。
msgmid 消息队列的标识号used-bytes 消息的字节长度messages 消息队列中的消息条数ipcrm在权限允许的情况下您可以使用ipcrm命令删除系统当前存在的IPC 对象中的任一个对象。
ipcrm -m 21482: 删除标号为21482 的共享内存。
ipcrm -s 32673: 删除标号为32673 的信号量数组。
ipcrm -q 18465: 删除标号为18465 的消息队列。
系统调用 IPC 对象有关的系统调用函数原型都声明在以下的头文件中 :
1 2 #include <sys/types.h> #include <sys/ipc.h>
其余参见指导书
共享内存 创建 创建一段共享内存系统调用语法 :
1 2 #include <sys/shm.h> int shmget (key_t key,int size,int flags) ;
key 共享内存的键值,可以为IPC_PRIVATE,也可以用整数指定一个
size 共享内存字节长度
flags 共享内存权限位。
shmget 调用成功后,如果key 用新整数指定,且flags 中设置了IPC_CREAT位,则返回一个新建立的共享内存段标识符。 如果指定的key 已存在则返回与key关联的标识符。 不成功返回-1
附加 令一段共享内存附加到调用进程中的系统调用语法:
1 2 #include <sys/shm.h> char *shmat (int shmid, char *shmaddr,int flags)
shmid 由shmget创建的共享内存的标识符
shmaddr 总为0,表示用调用者指定的指针指向共享段
flags 共享内存权限位
shmat调用成功后返回附加的共享内存首地址
分离 令一段共享内存从到调用进程中分离出去的系统调用语法:
1 2 #include <sys/shm.h> int shmdt (char *shmadr) ;
shmadr 进程中指向附加共享内存的指针
shmdt 调用成功将递减附加计数,当计数为0,将删除共享内存。调用不成功返回-1。
信号量 创建 创建一个信号量数组的系统调用有语法:
1 2 #include <sys/sem.h> int semget (key_t key,int nsems, int flags) ;
key 信号量数组的键值,可以为IPC_PRIVATE,也可以用整数指定一个
nsems 信号量数组中信号量的个数
flags 信号量数组权限位。如果key用整数指定,应设置IPC_CREAT 位。
semget调用成功,如果key用新整数指定,且flags 中设置了IPC_CREAT 位,则返回一个新建立的信号等数组标识符。 如果指定的整数key已存在则返回与key关联的标识符。 不成功返回-1
操作 操作信号量数组的系统调用语法:
1 2 #include <sys/sem.h> int semop (int semid,struct sembuf *semop, unsigned nops) ;
semid 由semget创建的信号量数组的标识符
semop 指向sembuf数据结构的指针
nops 信号量上的操作数,例如该值为1 相当于P操作,-1 相当于V操作.
semop调用成功返回0,不成功返回-1。
控制 控制信号量数组的系统调用语法:
1 2 #include <sys/sem.h> int semctl (int semid,int semnum,int cmd, union semun arg) ;
semid 由semget创建的信号量数组的标识符
semnum 该信号量数组中的第几个信号量
cmd 对信号量发出的控制命令。例如:
GETVAL 返回当前信号量状态
SETVAL 设置信号量状态
IPC_RMD 删除标号为semid的信号量
arg 保存信号量状态的联合体,信号量的值是其中一个基本成员
1 2 3 4 union semun {int val; ...... };
semctl 执行不成功返回-1,否则返回指定的cmd的值。
semget() semget()函数既可以用于获取之前创建的信号量集合,也可以用于创建新的信号量集合。semget()的函数原型如下:
1 2 int semget (key_t key, int nsems, int semflg) ;
key是绑定在信号量集合上的,我们可以用key来寻找已经创建的信号量集合,或者用于创建并绑定新的信号量集合。注意,key和函数的返回值(信号量集合)不是一样的。nsems表示该信号量集合有多少个信号量,如果用信号量来创建binary semaphore,我们只需要将nsems设置为1。semflg是设置信号量集合的标志位,其中最低9位是信号量集合的访问权限(和文件一样,3个8进制数字,比如0777)。其他位和文件创建的标志位类似,比如IPC_CREAT是创建新的信号量集合(文件是O_CREAT),在使用信号量前,我们都需要创建信号量集合。
在调用该系统调用后,与信号量集合绑定的数据结构semid_ds也被初始化。
semop() semop()用于操作信号量,简单来说就是对信号量的计数器进行加减。函数原型如下:
1 int semop (int semid, struct sembuf *sops, size_t nsops) ;
semid是信号量集合的标识符,注意,这不是semget()中的key而是这个函数的返回值。nsops是有多少个信号量需要被操作,在我们的例子中只有1个信号量需要被操作。sops是对信号量操作的具体命令,它是一个struct sembuf类型的结构体,一共有nsops个这样的结构体:
1 2 3 unsigned short sem_num; short sem_op; short sem_flg;
sem_num是从0开始计数的,表示是第几个信号量;sem_flg可以选择IPC_NOWAIT和SEM_UNDO,SEM_UNDO表示在进程结束后,内核会自动释放没有主动释放的信号量。sops是按照数组的顺序执行的,并且是原子的,如果所有的信号量不能同时操作,那么就不进行操作。
sem_op的使用很简单,但具体如何设置、不同设置有什么不同却比较麻烦。在binary semaphore的例子中,sem_op设置为1就是信号量计数器加1;设置为-1就是信号量计数器减1,下面具体讲讲sem_op设置背后的故事。
除了上面提到的sembuf结构体,每个信号量还对应一系列变量:
1 2 3 4 unsigned short semval; unsigned short semzcnt; unsigned short semncnt; pid_t sempid;
之前提到的信号量计数器就是semval,semzcnt是等待semval为0的进程数,semncnt是等待semval增加的进程数。semop()对信号量的操作都会改变这些变量。
如果sem_op设置为正数,那么每次操作后semval将变成semval + sem_op;如果sem_flg设置了SEM_UNDO,那么semadj将变成semadj - sem_op。进程需要有写的权限才能修改信号量。
如果sem_op设置为负数,情况要复杂些:
如果abs(sem_op)小于等于semval,那么semval将变成semval - abs(sem_op);如果sem_flg设置了SEM_UNDO,那么semadj将变成semadj + abs(sem_op); 如果abs(sem_op)大于semval并且sem_flg设置了IPC_NOWAIT,那么将返回错误码EAGAIN,并且sem_op的操作不会进行; 如果2中没有设置IPC_NOWAIT,那么semval将变成semval - abs(sem_op),同时semncnt 变成semncnt + 1,并且进程会进入sleep状态。当semval >= abs(sem_op)时,进程会被唤醒。 其他具体细节请参考man semop 。
从加锁和解锁的角度进行思考,当semval = 0时,如果操作是sem_op = -1,那么semval将变成-1,因此进程进入睡眠状态:这就是加锁的操作;同理,sem_op = 1是解锁的操作。
semctl() 通过上面两个系统调用,我们知道如何创建信号量以及如何操作信号量,下面这个系统调用可以初始化信号量以及删除信号量。我们先来看看semctl()的函数原型:
1 int semctl (int semid, int semnum, int cmd, ...) ;
semctl()支持的命令有很多,这里我们主要介绍两个 - IPC_RMID和SETVAL。semid是信号量集合的索引,semnum是其中第几个信号量(从0开始),cmd就是命令。这个函数是可变长度参数,有些命令是4个参数,第4个参数是union semun,这个union定义如下,注释中注明了哪些命令要用这个union:
1 2 3 4 5 6 union semun { int val; struct semid_ds *buf ; unsigned short *array ; struct seminfo *__buf ; };
使用SETVAL命令时,我们就是通过val给semval赋值,用于初始化信号量。我们这里看看如何使用semctl()来初始化和删除信号量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int set_sem (int sem_id) { union semun sem_union ; sem_union.val = 1 ; if (semctl(sem_id, 0 , SETVAL, sem_union) == -1 ) { fprintf (stderr , "Failed to set sem\n" ); return 0 ; } return 1 ; } void del_sem (int sem_id) { union semun sem_union ; if (semctl(sem_id, 0 , IPC_RMID, sem_union) == -1 ) fprintf (stderr , "Failed to delete sem, sem has been del.\n" ); }
参考:https://rdou.github.io/2020/06/22/Linux编程基础3-进程间通信-信号量/
基本逻辑 生产者:首先如果缓冲区满则生产者阻塞,其次生产者不能同时进行提供材料,所以要设置互斥锁使其不能同时进行执行。生产者每次提供其中的两种后,唤醒抽烟者。
抽烟者:而对于抽烟者来说,刚开始并没有所需要的材料,要等待生产者的唤醒,同时还要设置互斥锁防止抽烟者同时进行吸烟。判断当前放的两个材料是否是其所需,是则从共享缓冲区间读取所需要的数据,然后唤起两个生产者和其他抽烟者。若不是,则唤醒其他抽烟者抽烟。
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 semaphore tobacco_paper = 0 ; semaphore tobacco_matches = 0 ; semaphore paper_matches = 0 ; semaphore doneSomking = 0 ; while ( true ) { pick a random number from 1 -3 if random number is 1 signal( tobacco_paper ) else if random number is 2 signal( tobacco_matches ) else if random number is 3 signal( paper_matches ) wait( doneSmoking ) } while (true ){ wait( tobacco_paper ); signal( doneSmoking ); } while (true ){ wait(tobacco_matches); signal( doneSmoking ); } while (true ){ wait(matches_paper ); signal( doneSmoking ); }
Linux下的代码设计 参考
①关系分析:供应者与三个抽烟者分别是同步关系。由于抽烟者无法同时满足两个或以上的抽烟者,三个抽烟者对抽烟这个动作互斥(或由三个抽烟者轮流抽烟得知)。
②整体思路:一共设计六个文件,ipc.h和ipc.c设计信号量机制实现的底层逻辑,例如消息队列、P操作、V操作等。设计四个进程,供应者作为生产者向三个抽烟者提供材料。
③信号量设置:信号量offer1、offer2、offer3分别表示烟草和纸组合,烟草和胶水组合,胶水和纸组合。信号量finish表示抽烟完成信号。它们之间的协同关系如下图:
同时还要设置一个互斥信号量 ,保证4个进程互斥访问缓冲区,但该互斥信号量不是必须的,因为本例中缓冲区大小只为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 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <sys/msg.h> #define BUFSZ 256 int get_ipc_id (char *proc_file,key_t key) ;char *set_shm (key_t shm_key,int shm_num,int shm_flag) ;int set_msq (key_t msq_key,int msq_flag) ;int set_sem (key_t sem_key,int sem_val,int sem_flag) ;int down (int sem_id) ;int up (int sem_id) ;typedef union semuns { int val; } Sem_uns; typedef struct msgbuf { long mtype; char mtext[1 ]; } Msg_buf; key_t buff_key;int buff_num;char *buff_ptr;key_t pput_key;int pput_num;int *pput_ptr;key_t cget_key;int cget_num;int *cget_ptr;key_t offer1_key;key_t offer2_key;key_t offer3_key;key_t finish_key;key_t mutex_key;int offer1;int offer2;int offer3;int finish;int mutex;int sem_val;int sem_flg;int shm_flg;
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 #include "ipc.h" int get_ipc_id (char *proc_file, key_t key) { FILE *pf; int i,j; char line[BUFSZ], colum[BUFSZ]; if ((pf = fopen(proc_file,"r" )) == NULL ) { perror("Proc file not open" ); exit (EXIT_FAILURE); } fgets(line, BUFSZ, pf); while (!feof(pf)) { i = j = 0 ; fgets(line, BUFSZ,pf); while (line[i] == ' ' ) i++; while (line[i] != ' ' ) colum[j++] = line[i++]; colum[j] = '\0' ; if (atoi(colum) != key) continue ; j = 0 ; while (line[i] == ' ' ) i++; while (line[i] !=' ' ) colum[j++] = line[i++]; colum[j] = '\0' ; i = atoi(colum); fclose(pf); return i; } fclose(pf); return -1 ; } int down (int sem_id) { struct sembuf buf ; buf.sem_num = 0 ; buf.sem_op = -1 ; buf.sem_flg = SEM_UNDO; if ((semop(sem_id,&buf,1 )) < 0 ) { perror("P error " ); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } int up (int sem_id) { struct sembuf buf ; buf.sem_op = 1 ; buf.sem_num = 0 ; buf.sem_flg = SEM_UNDO; if ((semop(sem_id,&buf,1 )) < 0 ) { perror("V error " ); exit (EXIT_FAILURE); } return EXIT_SUCCESS; } int set_sem (key_t sem_key, int sem_val, int sem_flg) { int sem_id; Sem_uns sem_arg; if ((sem_id = get_ipc_id("/proc/sysvipc/sem" , sem_key)) < 0 ) { if ((sem_id = semget(sem_key, 1 , sem_flg)) < 0 ) { perror("semaphore create error" ); exit (EXIT_FAILURE); } sem_arg.val = sem_val; if (semctl(sem_id, 0 , SETVAL, sem_arg) < 0 ) { perror("semaphore set error" ); exit (EXIT_FAILURE); } } return sem_id; } char * set_shm (key_t shm_key, int shm_num, int shm_flg) { int i, shm_id; char * shm_buf; if ((shm_id = get_ipc_id("/proc/sysvipc/shm" , shm_key)) < 0 ) { if ((shm_id = shmget(shm_key, shm_num, shm_flg)) < 0 ) { perror("shareMemory set error" ); exit (EXIT_FAILURE); } if ((shm_buf = (char *)shmat(shm_id, 0 , 0 )) < (char *)0 ) { perror("get shareMemory error" ); exit (EXIT_FAILURE); } for (i = 0 ; i < shm_num; i++) shm_buf[i] = 0 ; } if ((shm_buf = (char *)shmat(shm_id, 0 , 0 )) < (char *)0 ) { perror("get shareMemory error" ); exit (EXIT_FAILURE); } return shm_buf; } int set_msq (key_t msq_key,int msq_flg) { int msq_id; if ((msq_id = get_ipc_id("/proc/sysvipc/msg" , msq_key)) < 0 ) { if ((msq_id = msgget(msq_key,msq_flg)) < 0 ) { perror("messageQueue set error" ); exit (EXIT_FAILURE); } } return msq_id; }
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 #include "ipc.h" #include <stdlib.h> int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 1 ; buff_key = 101 ; buff_num = 1 ; pput_key = 102 ; pput_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); pput_ptr = (int *)set_shm(pput_key, pput_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer1 = set_sem(offer1_key, sem_val, sem_flg); offer2 = set_sem(offer2_key, sem_val, sem_flg); offer3 = set_sem(offer3_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); int i = 0 ; while (1 ) { i = (i + 1 ) % 3 ; buff_ptr[*pput_ptr] = i + 1 ; sleep(rate); down(mutex); *pput_ptr = (*pput_ptr + 1 ) % buff_num; if (i == 0 ) { printf ("%d put offer1 - tobacco and paper into buffer[%d]\n" , getpid(), *pput_ptr); up(offer1); } else if (i == 1 ) { printf ("%d put offer2 - glue and tobacco into buffer[%d]\n" , getpid(), *pput_ptr); up(offer2); } else { printf ("%d put offer3 - glue and paper into buffer[%d]\n" , getpid(), *pput_ptr); up(offer3); } up(mutex); down(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer3 = set_sem(offer3_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer3); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get glue and paper offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer2 = set_sem(offer2_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer2); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get glue and tobacco offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 #include "ipc.h" int main (int argc,char *argv[]) { int rate; if (argv[1 ] != NULL ) rate = atoi(argv[1 ]); else rate = 3 ; buff_key = 101 ; buff_num = 1 ; cget_key = 103 ; cget_num = 1 ; shm_flg = IPC_CREAT | 0644 ; buff_ptr = (char *)set_shm(buff_key, buff_num, shm_flg); cget_ptr = (int *)set_shm(cget_key, cget_num, shm_flg); offer1_key = 201 ; offer2_key = 202 ; offer3_key = 203 ; finish_key = 204 ; mutex_key = 205 ; sem_flg = IPC_CREAT | 0644 ; sem_val = 0 ; offer1 = set_sem(offer1_key, sem_val, sem_flg); finish = set_sem(finish_key, sem_val, sem_flg); sem_val = 1 ; mutex = set_sem(mutex_key, sem_val, sem_flg); while (1 ) { sleep(rate); down(offer1); down(mutex); *cget_ptr = (*cget_ptr + 1 ) % buff_num; printf ("%d smoker get tobacco and paper offer%d from buffer[%d]\n" , getpid(), buff_ptr[*cget_ptr], *cget_ptr); up(mutex); up(finish); } return EXIT_SUCCESS; }
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 hdrs = ipc.h opts = -g -c p_src = producer.c ipc.c p_obj = producer.o ipc.o c_T_src = consumer_tobacco.c ipc.c c_T_obj = consumer_tobacco.o ipc.o c_P_src = consumer_paper.c ipc.c c_P_obj = consumer_paper.o ipc.o c_G_src = consumer_glue.c ipc.c c_G_obj = consumer_glue.o ipc.o all: producer consumer_tobacco consumer_paper consumer_glue producer: $(p_obj) gcc $(p_obj) -o producer producer.o: $(p_src) $(hdrs) gcc $(opts) $(p_src) consumer_tobacco: $(c_T_obj) gcc $(c_T_obj) -o consumer_tobacco consumer_tobacco.o: $(c_T_src) $(hdrs) gcc $(opts) $(c_T_src) consumer_paper: $(c_P_obj) gcc $(c_P_obj) -o consumer_paper consumer_paper.o: $(c_P_src) $(hdrs) gcc $(opts) $(c_P_src) consumer_glue: $(c_G_obj) gcc $(c_G_obj) -o consumer_glue consumer_glue.o: $(c_G_src) $(hdrs) gcc $(opts) $(c_G_src) clean: rm producer consumer_tobacco consumer_paper consumer_glue *.o
一些问题 由于该程序会修改文件/proc/sysvipc/sem中的记录,且运行完后不会删除,因此如果多次对文件进行修改,并make后执行,可能使用到一个错误的信号量id,对该信号量的操作不会反映到程序中,程序会直接跳过对信号量的P操作和V操作。解决方法是重启虚拟机。 比如,就会这样:
finish同步信号量的设置可以为1或0,如果是1,则应该在while循环开始处调用P操作,这样第一遍循环可以顺利执行,阻塞在第二次循坏的开始处;如果是0,则阻塞在while循坏的尾部。