进程间通信第二课--信号量 共享内存 消息队列

信号量

程序中存在一部分临界代码,要确保只有一个进程(或一个执行线程)可以进入临界区代码,并拥有对资源的独占式访问权

我们需要一种方法,通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域

这里讲的信号量比在线程的调用中使用的互斥量和信号量更加通用

P:等待,好像位于进入临界区域之前的检查点

V:给予或者释放,好像放弃对临界区域的控制权

P(sv):要是sv的值大于零,就减去1,如果它的值等于零,就挂起该进程的执行

V(sv):要是其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就把sv加1

【或者是这样的情况,当临界区域可用时,信号量sv的值是true,然后P(sv)操作将它减1使它变为false以表示临界区域正在被使用;

当进程离开临界区域时,使用V(sv)操作将它加1,使临界区域再次变为可用,但是任何时候只有一个进程或者线程进入临界区域】

1 int semget(key_t key, int num_sems, int sem_flags);

函数作用是创建一个信号量或取得一个已有信号量的键

1 int semop(int sem_id, struct sembuf * sem_ops, size_t num_sem_ops);

函数的作用是用于改变信号量的值

1 int semctl(int sem_id, int sem_num, int command,...);

函数的作用是允许我们直接控制信号量信息

使用信号量的例程:

  1 /* After the #includes, the function prototypes and the global variable, we come to the
  2  main function. There the semaphore is created with a call to semget, which returns the
  3  semaphore ID. If the program is the first to be called (i.e. it's called with a parameter
  4  and argc > 1), a call is made to set_semvalue to initialize the semaphore and op_char is
  5  set to X. */
  6 
  7 #include <unistd.h>
  8 #include <stdlib.h>
  9 #include <stdio.h>
 10 
 11 #include <sys/types.h>
 12 #include <sys/ipc.h>
 13 #include <sys/sem.h>
 14 
 15 #include "semun.h"//包含必要的头文件
 16 
 17 static int set_semvalue(void);//声明了函数原形
 18 static void del_semvalue(void);
 19 static int semaphore_p(void);
 20 static int semaphore_v(void);
 21 
 22 static int sem_id;//定义全局变量
 23 
 24 
 25 int main(int argc, char *argv[])
 26 {
 27     int i;
 28     int pause_time;
 29     char op_char = 'O';
 30 
 31     srand((unsigned int)getpid());
 32     
 33     sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);//获取一个信号量要是不存在就创建
 34 
 35     if (argc > 1) {//要是函数带有参数
 36         if (!set_semvalue()) {
 37             fprintf(stderr, "Failed to initialize semaphore
");
 38             exit(EXIT_FAILURE);
 39         }
 40         op_char = 'X';//设置信号量为X
 41         sleep(2);
 42     }
 43 
 44 /* Then we have a loop which enters and leaves the critical section ten times.
 45  There, we first make a call to semaphore_p which sets the semaphore to wait, as
 46  this program is about to enter the critical section. */
 47 
 48     for(i = 0; i < 10; i++) {        
 49 
 50         if (!semaphore_p()) exit(EXIT_FAILURE);//调用这个函数,程序将进入临界区域,设置信号量以等待进入。
 51         printf("%c", op_char);fflush(stdout);
 52         pause_time = rand() % 3;//等待随机时间
 53         sleep(pause_time);
 54         printf("%c", op_char);fflush(stdout);
 55 
 56 /* After the critical section, we call semaphore_v, setting the semaphore available,
 57  before going through the for loop again after a random wait. After the loop, the call
 58  to del_semvalue is made to clean up the code. */
 59 
 60         if (!semaphore_v()) exit(EXIT_FAILURE);//调用这个函数,将信号量设置为可用,然后等待一段随机时间
 61         //相当于离开临界区
 62         pause_time = rand() % 2;
 63         sleep(pause_time);
 64     }    //循环十次
 65 
 66     printf("
%d - finished
", getpid());
 67 
 68     if (argc > 1) {    
 69         sleep(10);
 70         del_semvalue();//整循环执行完毕之后,调用这个函数去清理代码
 71     }
 72         
 73     exit(EXIT_SUCCESS);
 74 }
 75 
 76 /* The function set_semvalue initializes the semaphore using the SETVAL command in a
 77  semctl call. We need to do this before we can use the semaphore. */
 78 
 79 static int set_semvalue(void)
 80 {
 81     union semun sem_union;
 82 
 83     sem_union.val = 1;//设置信号量的值,在使用信号量之前必须这样做
 84     if (semctl(sem_id, 0, SETVAL, sem_union) == -1) return(0);
 85     return(1);
 86 }
 87 
 88 /* The del_semvalue function has almost the same form, except the call to semctl uses
 89  the command IPC_RMID to remove the semaphore's ID. */
 90 
 91 static void del_semvalue(void)//删除信号量ID
 92 {
 93     union semun sem_union;
 94     
 95     if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
 96         fprintf(stderr, "Failed to delete semaphore
");
 97 }
 98 
 99 /* semaphore_p changes the semaphore by -1 (waiting). */
100 
101 static int semaphore_p(void)//对信号量做减1操作
102 {
103     struct sembuf sem_b;
104     
105     sem_b.sem_num = 0;
106     sem_b.sem_op = -1; /* P() */
107     sem_b.sem_flg = SEM_UNDO;
108     if (semop(sem_id, &sem_b, 1) == -1) {
109         fprintf(stderr, "semaphore_p failed
");
110         return(0);
111     }
112     return(1);
113 }
114 
115 /* semaphore_v is similar except for setting the sem_op part of the sembuf structure to 1,
116  so that the semaphore becomes available. */
117 
118 static int semaphore_v(void)//使得信号量可用
119 {
120     struct sembuf sem_b;
121     
122     sem_b.sem_num = 0;
123     sem_b.sem_op = 1; /* V() */
124     sem_b.sem_flg = SEM_UNDO;
125     if (semop(sem_id, &sem_b, 1) == -1) {
126         fprintf(stderr, "semaphore_v failed
");
127         return(0);
128     }
129     return(1);
130 }

如果程序启动时带有一个参数,它将在进入和退出临界区域时打印字符X

而程序的其他实例将在进入和退出临界区域时打印字符o

因为在任一时刻,只能有一个进程可以进入临界区域,所以字符x和o应该是成对出现的

执行的效果:

1 jason@t61:~/c_program/544977-blp3e/chapter14$ ./sem1 qwqw & ./sem1
2 [2] 22102
3 OOOOXXOOXXOOXXXXXXOOXXOOXXOOXXOOXXOOOO
4 22103 - finished
5 X[1]   已完成               ./sem1 1
6 jason@t61:~/c_program/544977-blp3e/chapter14$ X
7 22102 - finished
8 
9 jason@t61:~/c_program/544977-blp3e/chapter14$ 

共享内存:

允许两个不相关的进程访问同一个逻辑内存

是在两个正在运行的进程之间传递数据的一种非常有效的方式

为在多个进程之间共享和传递数据提供了一种非常有效的方式

共享内存本身并未提供任何同步机制,在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进行读取

对共享内存访问的同步控制必须由程序员来负责。

1 int shmget(key_t key, size_t size, int shmflg);

函数的作用是用来创建共享内存

1 void * shmat(int shm_id, const void * shm_addr, int shmflg);

函数的作用是保证第一次创建共享内存时,不能被任何进程访问,要想启用对共享内存的访问,必须将其连接到一个进程的地址空间

1 int shmdt(const void * shm_addr);

函数的作用是将共享内存凑从当前进程中分离,不是删除。成功返回0

1 int shmctl(int shm_id, int command, struct shmid_ds * buf);

函数的作用是对共享内存的控制

共享内存的例程:

shm_com.h

1 /* A common header file to describe the shared memory we wish to pass about. */
2 
3 #define TEXT_SZ 2048//2k
4 //创建一个公共的头文件,定义我们希望分发的共享内存
5 struct shared_use_st {
6     int written_by_you;//通知消费数据的程序
7     char some_text[TEXT_SZ];
8 };

shm1.c

 1 /* Our first program is a consumer. After the headers the shared memory segment
 2  (the size of our shared memory structure) is created with a call to shmget,
 3  with the IPC_CREAT bit specified. */
 4 
 5 #include <unistd.h>
 6 #include <stdlib.h>
 7 #include <stdio.h>
 8 #include <string.h>
 9 
10 #include <sys/types.h>
11 #include <sys/ipc.h>
12 #include <sys/shm.h>
13 
14 #include "shm_com.h"
15 
16 int main()
17 {
18     int running = 1;
19     void *shared_memory = (void *)0;
20     struct shared_use_st *shared_stuff;
21     int shmid;
22 
23     srand((unsigned int)getpid());    
24 
25     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);//创建共享内存段
26 
27     if (shmid == -1) {
28         fprintf(stderr, "shmget failed
");
29         exit(EXIT_FAILURE);
30     }
31 
32 /* We now make the shared memory accessible to the program. */
33 
34     shared_memory = shmat(shmid, (void *)0, 0);//让程序可以访问这个共享内存
35     if (shared_memory == (void *)-1) {
36         fprintf(stderr, "shmat failed
");
37         exit(EXIT_FAILURE);
38     }
39 
40     printf("Memory attached at %X
", (int)shared_memory);
41 
42 /* The next portion of the program assigns the shared_memory segment to shared_stuff,
43  which then prints out any text in written_by_you. The loop continues until end is found
44  in written_by_you. The call to sleep forces the consumer to sit in its critical section,
45  which makes the producer wait. */
46 
47     shared_stuff = (struct shared_use_st *)shared_memory;//将共享内存段分配到shared_stuff
48     shared_stuff->written_by_you = 0;//设置这个量为0
49     while(running) {
50         if (shared_stuff->written_by_you) {
51             printf("You wrote: %s", shared_stuff->some_text);
52             sleep( rand() % 4 ); /* make the other process wait for us ! */
53             shared_stuff->written_by_you = 0;
54             if (strncmp(shared_stuff->some_text, "end", 3) == 0) {
55                 running = 0;//程序一直执行到some_text中有end字符串为止
56             }
57         }
58     }
59 
60 /* Lastly, the shared memory is detached and then deleted. */
61 
62     if (shmdt(shared_memory) == -1) {//分离共享内存
63         fprintf(stderr, "shmdt failed
");
64         exit(EXIT_FAILURE);
65     }
66 
67     if (shmctl(shmid, IPC_RMID, 0) == -1) {//删除共享内存
68         fprintf(stderr, "shmctl(IPC_RMID) failed
");
69         exit(EXIT_FAILURE);
70     }
71 
72     exit(EXIT_SUCCESS);
73 }

shm2.c

 1 /* The second program is the producer and allows us to enter data for consumers.
 2  It's very similar to shm1.c and looks like this. */
 3 
 4 #include <unistd.h>
 5 #include <stdlib.h>
 6 #include <stdio.h>
 7 #include <string.h>
 8 
 9 #include <sys/types.h>
10 #include <sys/ipc.h>
11 #include <sys/shm.h>
12 
13 #include "shm_com.h"
14 
15 int main()
16 {
17     int running = 1;
18     void *shared_memory = (void *)0;
19     struct shared_use_st *shared_stuff;
20     char buffer[BUFSIZ];
21     int shmid;
22 
23     shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);//连接到一个共享内存段
24 
25     if (shmid == -1) {
26         fprintf(stderr, "shmget failed
");
27         exit(EXIT_FAILURE);
28     }
29 
30     shared_memory = shmat(shmid, (void *)0, 0);
31     if (shared_memory == (void *)-1) {
32         fprintf(stderr, "shmat failed
");
33         exit(EXIT_FAILURE);
34     }
35 
36     printf("Memory attached at %X
", (int)shared_memory);
37 
38     shared_stuff = (struct shared_use_st *)shared_memory;
39     while(running) {
40         while(shared_stuff->written_by_you == 1) {
41             sleep(1);            //这个量被设置说明客户端还未完成上一段的读写,继续等待
42             printf("waiting for client...
");
43         }//要是written_by_you清理之后,
44         printf("Enter some text: ");
45         fgets(buffer, BUFSIZ, stdin);//读入新数据,
46         
47         strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
48         shared_stuff->written_by_you = 1;
49 
50         if (strncmp(buffer, "end", 3) == 0) {
51                 running = 0;//end匹配之后结束循环
52         }
53     }
54 
55     if (shmdt(shared_memory) == -1) {//分离共享内存段
56         fprintf(stderr, "shmdt failed
");
57         exit(EXIT_FAILURE);
58     }
59     exit(EXIT_SUCCESS);
60 }

程序的执行的效果:

 1 jason@t61:~/c_program/544977-blp3e/chapter14$ ./shm1 & ./shm2
 2 [1] 1704
 3 Memory attached at B77CF000
 4 Enter some text: Memory attached at B7725000
 5 hello
 6 You wrote: hello
 7 waiting for client...
 8 Enter some text: world
 9 You wrote: world
10 waiting for client...
11 waiting for client...
12 Enter some text: ende 
13 You wrote: ende
14 jason@t61:~/c_program/544977-blp3e/chapter14$ 

消息队列:

消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法

每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据块

msg1.c

 1 /* Here's the receiver program. */
 2 
 3 #include <stdlib.h>
 4 #include <stdio.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <unistd.h>
 8 
 9 #include <sys/types.h>
10 #include <sys/ipc.h>
11 #include <sys/msg.h>
12 
13 
14 struct my_msg_st {
15     long int my_msg_type;
16     char some_text[BUFSIZ];
17 };
18 
19 int main()
20 {
21     int running = 1;
22     int msgid;
23     struct my_msg_st some_data;
24     long int msg_to_receive = 0;
25 
26 /* First, we set up the message queue. */
27 
28     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);//创建消息队列 
29 
30     if (msgid == -1) {
31         fprintf(stderr, "msgget failed with error: %d
", errno);
32         exit(EXIT_FAILURE);
33     }
34 
35 /* Then the messages are retrieved from the queue, until an end message is encountered.
36  Lastly, the message queue is deleted. */
37 
38     while(running) {
39         if (msgrcv(msgid, (void *)&some_data, BUFSIZ,
40               msg_to_receive, 0) == -1) {
41             fprintf(stderr, "msgrcv failed with error: %d
", errno);
42             exit(EXIT_FAILURE);//从消息队列中获取消息
43         }
44         printf("You wrote: %s", some_data.some_text);
45         if (strncmp(some_data.some_text, "end", 3) == 0) {
46             running = 0;//直到遇到end为止
47         }
48     }
49 
50     if (msgctl(msgid, IPC_RMID, 0) == -1) {
51         fprintf(stderr, "msgctl(IPC_RMID) failed
");
52         exit(EXIT_FAILURE);//删除消息队列
53     }
54 
55     exit(EXIT_SUCCESS);
56 }

msg2.c

 1 /* The sender program is very similar to msg1.c. In the main set up, delete the
 2  msg_to_receive declaration and replace it with buffer[BUFSIZ], remove the message
 3  queue delete and make the following changes to the running loop.
 4  We now have a call to msgsnd to send the entered text to the queue. */
 5 
 6 #include <stdlib.h>
 7 #include <stdio.h>
 8 #include <string.h>
 9 #include <errno.h>
10 #include <unistd.h>
11 
12 #include <sys/types.h>
13 #include <sys/ipc.h>
14 #include <sys/msg.h>
15 
16 #define MAX_TEXT 512
17 
18 struct my_msg_st {
19     long int my_msg_type;
20     char some_text[MAX_TEXT];
21 };
22 
23 int main()
24 {
25     int running = 1;
26     struct my_msg_st some_data;
27     int msgid;
28     char buffer[BUFSIZ];
29 
30     msgid = msgget((key_t)1234, 0666 | IPC_CREAT);//创建消息队列
31 
32     if (msgid == -1) {
33         fprintf(stderr, "msgget failed with error: %d
", errno);
34         exit(EXIT_FAILURE);
35     }
36 
37     while(running) {
38         printf("Enter some text: ");
39         fgets(buffer, BUFSIZ, stdin);
40         some_data.my_msg_type = 1;
41         strcpy(some_data.some_text, buffer);
42 
43         if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) {//发送输入到消息队列
44             fprintf(stderr, "msgsnd failed
");
45             exit(EXIT_FAILURE);
46         }
47         if (strncmp(buffer, "end", 3) == 0) {
48             running = 0;//遇到end消息为止
49         }
50     }
51 
52     exit(EXIT_SUCCESS);
53 }

程序的执行效果:

 1 jason@t61:~/c_program/544977-blp3e/chapter14$ ./msg2 
 2 Enter some text: hello
 3 Enter some text: world
 4 Enter some text: this is not the end?
 5 Enter some text: this is end ?
 6 Enter some text: endend
 7 jason@t61:~/c_program/544977-blp3e/chapter14$ ./msg1
 8 You wrote: hello
 9 You wrote: world
10 You wrote: this is not the end?
11 You wrote: this is end ?
12 You wrote: endend
13 jason@t61:~/c_program/544977-blp3e/chapter14$ 

万事走心 精益求美


原文地址:https://www.cnblogs.com/kongchung/p/4620234.html