11.2 进程的同步和互斥

  顺序程序与并发程序的特征:

顺序进程特征:

  顺序性

  封闭性(运行环境的封闭性)

  确定性

  可再现性

并发程序特征:

  共享性

  并发性

  随机性

进程互斥:

  由于各个进程要求共享资源,而且有些资源需要互斥使用,因此,各进程间竞争使用这些资源,进程的这种关系为进程的互斥。

  系统中的某些资源一次只允许一个进程使用,成这样的资源为临界资源或者互斥资源。

  在进程中涉及到互斥资源的程序段叫临界区。

进程同步:

  进程同步指的是多个进程需要相互配合共同完成一项任务。

进程间同步示例:

司机启动车辆前,要先看看门是否关好,关好了才能启动车辆。而售票员在开门前要先看看车是否停好了,停好了的话才能开门。这些动作之间有一定的顺序,这就是同步。

 进程间通信的目的:

  数据传输:一个进程需要将它的数据发送给另一个进程

  资源共享:多个进程之间共享同样的资源

  通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)

  进程控制:有些进程希望完全控制另一个进程的执行(如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
");
24         }
25         
26         perror("semget error");
27         exit(0);
28     }
29     
30     printf("semget success
");
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
");
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
", 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
");
 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
", 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查看具体用法。

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9439608.html