顺序程序与并发程序的特征:
顺序进程特征:
顺序性
封闭性(运行环境的封闭性)
确定性
可再现性
并发程序特征:
共享性
并发性
随机性
进程互斥:
由于各个进程要求共享资源,而且有些资源需要互斥使用,因此,各进程间竞争使用这些资源,进程的这种关系为进程的互斥。
系统中的某些资源一次只允许一个进程使用,成这样的资源为临界资源或者互斥资源。
在进程中涉及到互斥资源的程序段叫临界区。
进程同步:
进程同步指的是多个进程需要相互配合共同完成一项任务。
进程间同步示例:
司机启动车辆前,要先看看门是否关好,关好了才能启动车辆。而售票员在开门前要先看看车是否停好了,停好了的话才能开门。这些动作之间有一定的顺序,这就是同步。
进程间通信的目的:
数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
死锁:
信号量:
P、V操作的伪代码如下:
信号量的数据结构如下:
信号量相关的函数如下:
semget函数:
原型:int semget(key_t key, int nsems, int semflg)
功能:用来访问和创建一个信号量集
参数:
key:信号集的名字
nsems:信号集中信号量的个数
semflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1
semctl函数:
原型:int semctl(int semid, int semnum, int cmd, ...)
功能:用于控制信号量集
参数:
semid:由semget返回的信号量集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有5个可取值)
最后一个参数根据命令不同而不同
返回值:成功返回0,失败返回-1
可采取的动作如下:
semop函数:
原型:int semop(int semid, struct sembuf *sops, unsigned nsops)
功能:用来创建和访问一个信号量集
参数:
semid:信号量的标识码,就是semget函数的返回值
sops:是一个指向结构体的指针
nsops:信号量的个数
返回值:成功返回0,失败返回-1
sembuf结构如下:
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
};
sem_num:信号量的编号
sem_op:信号量一次PV操作时加减的数值,一般只会用到两个值,P操作一般取-1, V操作取1
sem_flag:有两个取值,IPC_NOWAIT和SEM_UNDO,当设置为SEM_UNDO时,若进程挂掉,则内核帮我们释放信号量,相当于进程挂掉时内核帮我们执行V操作
semget示例程序如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 #include <sys/ipc.h> 9 #include <sys/shm.h> 10 #include <sys/types.h> 11 #include <sys/ipc.h> 12 #include <sys/sem.h> 13 14 int main() 15 { 16 int semid = 0; 17 semid = semget(0x1111, 1, 0666 | IPC_CREAT | IPC_EXCL); 18 19 if(semid == -1) 20 { 21 if(errno == EEXIST) 22 { 23 printf("sem existed\n"); 24 } 25 26 perror("semget error"); 27 exit(0); 28 } 29 30 printf("semget success\n"); 31 32 return 0; 33 }
执行结果如下:
执行ipcs,结果如下:
semctl示例程序如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 #include <sys/ipc.h> 9 #include <sys/shm.h> 10 #include <sys/types.h> 11 #include <sys/ipc.h> 12 #include <sys/sem.h> 13 14 union semun { 15 int val; /* Value for SETVAL */ 16 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 17 unsigned short *array; /* Array for GETALL, SETALL */ 18 struct seminfo *__buf; /* Buffer for IPC_INFO 19 (Linux-specific) */ 20 }; 21 22 23 int sem_creat(key_t key) 24 { 25 int semid = 0; 26 semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL); 27 28 if(semid == -1) 29 { 30 if(errno == EEXIST) 31 { 32 printf("sem existed\n"); 33 } 34 35 perror("semget error"); 36 return 0; 37 } 38 39 return semid; 40 } 41 42 43 int sem_open(key_t key) 44 { 45 int semid = 0; 46 semid = semget(key, 1, 0666); 47 48 if(semid == -1) 49 { 50 if(errno == EEXIST) 51 { 52 perror("open sem error"); 53 return -1; 54 } 55 } 56 57 return semid; 58 } 59 60 int sem_setval(int semid, int val) 61 { 62 int ret = 0; 63 union semun su; 64 su.val = val; 65 66 ret = semctl(semid, 0, SETVAL, su); 67 68 return ret; 69 } 70 71 int sem_getval(int semid, int *val) 72 { 73 int ret = 0; 74 union semun su; 75 76 ret = semctl(semid, 0, GETVAL, su); 77 78 if(ret != -1) 79 { 80 *val = su.val; 81 } 82 83 return ret; 84 } 85 86 int main() 87 { 88 //sem_creat(0x1111); 89 int semid; 90 semid = sem_open(0x1111); 91 sem_setval(semid, 1); 92 93 int val = 0; 94 95 sem_getval(semid, &val); 96 printf("val = %d\n", val); 97 98 return 0; 99 }
因为系统中已经有了key值为0x1111的信号量,所以我们直接打开这个信号量,执行结果如下:
下一步我们封装P操作和V操作,程序如下:
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 #include <string.h> 5 #include <stdlib.h> 6 #include <errno.h> 7 #include <sys/msg.h> 8 #include <sys/ipc.h> 9 #include <sys/shm.h> 10 #include <sys/types.h> 11 #include <sys/ipc.h> 12 #include <sys/sem.h> 13 14 union semun { 15 int val; /* Value for SETVAL */ 16 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ 17 unsigned short *array; /* Array for GETALL, SETALL */ 18 struct seminfo *__buf; /* Buffer for IPC_INFO 19 (Linux-specific) */ 20 }; 21 22 23 int sem_creat(key_t key) 24 { 25 int semid = 0; 26 semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL); 27 28 if(semid == -1) 29 { 30 if(errno == EEXIST) 31 { 32 printf("sem existed\n"); 33 } 34 35 perror("semget error"); 36 return 0; 37 } 38 39 return semid; 40 } 41 42 43 int sem_open(key_t key) 44 { 45 int semid = 0; 46 semid = semget(key, 1, 0666); 47 48 if(semid == -1) 49 { 50 if(errno == EEXIST) 51 { 52 perror("open sem error"); 53 return -1; 54 } 55 } 56 57 return semid; 58 } 59 60 int sem_setval(int semid, int val) 61 { 62 int ret = 0; 63 union semun su; 64 su.val = val; 65 66 ret = semctl(semid, 0, SETVAL, su); 67 68 return ret; 69 } 70 71 int sem_getval(int semid, int *val) 72 { 73 int ret = 0; 74 union semun su; 75 76 ret = semctl(semid, 0, GETVAL, su); 77 78 if(ret != -1) 79 { 80 *val = su.val; 81 } 82 83 return ret; 84 } 85 86 int sem_p(int semid) 87 { 88 struct sembuf buf = {0, -1, 0}; 89 int ret = 0; 90 91 ret = semop(semid, &buf, 1); 92 return ret; 93 } 94 95 int sem_v(int semid) 96 { 97 struct sembuf buf = {0, 1, 0}; 98 int ret = 0; 99 100 ret = semop(semid, &buf, 1); 101 return ret; 102 } 103 104 int main() 105 { 106 //sem_creat(0x1111); 107 int semid; 108 semid = sem_open(0x1111); 109 sem_setval(semid, 1); 110 111 int val = 0; 112 113 sem_getval(semid, &val); 114 sem_p(semid); 115 printf("val = %d\n", val); 116 sem_v(semid); 117 118 return 0; 119 }
我们封装了PV操作,109行将信号量的值设置为1,可以允许一个进程进入,执行结果如下:
可以看到进程成功打印出了val的值,也就是成功进入了临界区。
现在我们更改第109行,将信号量的初始值设置为0,再次执行程序,结果如下:
由于运行完P操作之后进程进入睡眠状态,所以程序永远的停在了这里。
semtool小工具:
getopt函数提取命令行中的参数,存到opt中,第三个参数中的冒号(:)是一个占位符,后面的gfm用于提取第三个参数,然后放到optarg全局变量中。如果在命令行中找不到第三个参数中的值,会返回一个问号(?)。可以使用man 3 getopt查看具体用法。