首页 > 代码库 > Linux进程间的通信

Linux进程间的通信

一.管道

管道是Linux支持的最初Unix IPC形式之一,具有以下特点:
A. 管道是半双工的,数据只能向一个方向流动;
B. 需要双工通信时,需要建立起两个管道;
C. 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
D. 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。

匿名管道的创建:该函数创建的管道的两端处于一个进程中间,在实际应用中没有太大意义;因此,一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信。因此只要两个进程中存在亲缘关系(这里的亲缘关系指的是具有共同的祖先),都可以采用管道方式来进行通信。
 #include <unistd.h>
 int pipe(int fd[2]);

匿名管道的读写规则:数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。管道两端可分别用描述字fd[0]以及fd[1]来描述,需要注意的是,管道的两端是固定了任务的。即一端只能用于读,由描述字 fd[0]表示,称其为管道读端;另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据 都将导致错误发生。一般文件的I/O函数都可以用于管道,如close、read、write等等。从管道中读取数据:如果管道的写端不存在,则认为已经读到了数据的末尾,读函数返回的读出字节数为0;当管道的写端存在时,
如果请求的字节数目大于PIPE_BUF, 则返回管道中现有数据字节数。下面例子给出了管道的具体应用,父进程通过管道发送一些命令给子进程,子进程解析命令,并根据命令作相应处理。

技术分享
 1 #include <unistd.h>
 2 #include <sys/types.h>
 3 main()
 4 {
 5     int pipe_fd[2];
 6     pid_t pid;
 7     char r_buf[4];
 8     char** w_buf[256];
 9     int childexit=0;
10     int i;
11     int cmd;
12     
13     memset(r_buf,0,sizeof(r_buf));
14     if(pipe(pipe_fd)<0)
15     {
16         printf("pipe create error\n");
17         return -1;
18     }
19     if((pid=fork())==0)
20     //子进程:解析从管道中获取的命令,并作相应的处理
21     {
22         printf("\n");
23         close(pipe_fd[1]);
24         sleep(2);
25         
26         while(!childexit)
27         {    
28             read(pipe_fd[0],r_buf,4);
29             cmd=atoi(r_buf);
30             if(cmd==0)
31             {
32 printf("child: receive command from parent over\n now child process exit\n");
33                 childexit=1;
34             }
35             
36                else if(handle_cmd(cmd)!=0)
37                 return;
38             sleep(1);
39         }
40         close(pipe_fd[0]);
41         exit();
42     }
43     else if(pid>0)
44     //parent: send commands to child
45     {
46     close(pipe_fd[0]);
47     w_buf[0]="003";
48     w_buf[1]="005";
49     w_buf[2]="777";
50     w_buf[3]="000";
51     for(i=0;i<4;i++)
52         write(pipe_fd[1],w_buf[i],4);
53     close(pipe_fd[1]);
54     }    
55 }
56 //下面是子进程的命令处理函数(特定于应用):
57 int handle_cmd(int cmd)
58 {
59 if((cmd<0)||(cmd>256))
60 //suppose child only support 256 commands
61     {
62     printf("child: invalid command \n");
63     return -1;
64     }
65 printf("child: the cmd from parent is %d\n", cmd);
66 return 0;
67 }
View Code

二.命名管道FIFO

  命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但它的行为却和匿名管道类似FIFO文件与普通文件之间的区别:程序不能以O_RDWR模式打开FIFO文件进行读写操作,这样做的后果并未明确定义,如果一个管道以读/写方式打开, 进程就会从这个管道读回它自己的输出.如果需要在程序之间双向传递数据,最好是使用一对FIFO或管道,一个方向使用一个;或者(但不常用),采用先关闭再重新打开FIFO的方法来,明确改变数据流的方向.

open_flag标志(O_RDONLY, O_WRONLY和O_NONBLOCK)的4种合法组合方式,close调用行为并不受O_NONBLOCK标志的影响.
A. 阻塞方式读open: open(const char *path, O_RDONLY);在这种方式下,open调用将阻塞,除非有一个进程以写的方式打开同一个FIFO,否则它不会
返回。
B. 非阻塞方式读:openopen(const char *path, O_RDONLY | O_NONBLOCK);即使没有其它的进程以写的方式打开FIFO,这个open调用也将成功并立即
返回。
C. 阻塞方式写open:open(const char *path, O_WRONLY);在这种情况下,open调用将阻塞,直到有一个进程以读方式打开同一个FIFO为止。
D. 非阻塞方式写open:open(const char *path, O_WRONLY | O_NONBLOCK)这个函数调用总是立即返回,如果没有进程以读方式打开FIFO文件,open调用将返回一个错误-1并且FIFO也不会打开。如果 有一个进程以读方式打开FIFO文件,那么可以通过这个函数返回的文件描述符对这个FIFO进行写操作。

open模式的最常见的组合形式:
A. 阻塞方式读open + 阻塞方式写open 这样的形式, 它允许先启动读进程,并在open调用中等待,当写进程打开FIFO时,两个进程在open调用处取得同步,两个程序继续运行。
B. 非阻塞方式读open + 阻塞方式写open这时,读进程在即使没有写进程存在的情况下,仍能执行open调用并继续执行.随后写进程开始执行,因为FIFO已被读进程打开,它在open调用后立即继续执行.

使用O_NONBLOCK对FIFO的read和write操作约定:对一个空的,阻塞的FIFO(即没有用O_NONBLOCK标志打开)的read调用将等待,直到有数据可以读时才执行。与此相反,对一个空的,非阻塞的FIFO的read调用立即返回0字节。对一个完全阻塞FIFO的write调用将等待,直到数据可以被写入时才继续执行。如果FIFO不能接收所有写入的数据,它将按下面的规则执行:如果请求写入的数据长度 <= PIPE_BUF字节,调用失败,数据不能写入。如果请求写放的数据长度 > PIPE_BUF字节,将写入部分数据,返回实际写入的字节数,返回值也可能是0.FIFO的长度定义在limits.h中的#define PIPE_BUG语句中定义,linux下的值通常是4096字节。

FIFO的原子性:系统规定, 在一个以O_WRONLY方式打开的FIFO中,如果写入的数据长度 <= PIPE_BUF字节,那么或者写入全部字节,或者一个字节都不写入。如果保证所有写请求是发往一个阻塞的FIFO的,并且每个写请求的数据长度 <= PIPE_BUF字节,系统将会保证数据决不会交错在一起。所以,通常将每次通过FIFO传递的数据长度限制为PIPE_BUF字节是个好方法。

技术分享
  1 /*示例, 生产者--消费者模型
  2 以阻塞方式读open + 阻塞方式写open;
  3 写进程读取文件数据并发送到FIFO;
  4 读进程读取FIFO中的数据并显示;
  5 
  6 代码如下:
  7 producer.c*/
  8 
  9 /*
 10  * \File
 11  * producer.c
 12  *
 13  */
 14 
 15 #include <unistd.h>
 16 #include <stdlib.h>
 17 #include <stdio.h>
 18 #include <string.h>
 19 #include <fcntl.h>
 20 #include <limits.h>
 21 #include <sys/types.h>
 22 #include <sys/stat.h>
 23 
 24 #define FIFO_NAME "test_fifo"
 25 #define BUFFER_SIZE (512)
 26 
 27 #define TESTF_IN "test.dat"
 28 #define OP_LEN (100)
 29 
 30 FILE* fp_in;
 31 
 32 int main(char argc, char* argv[])
 33 {
 34     int pipe_fd;
 35     int res;
 36     int open_mode = O_WRONLY;
 37     int bytes_sent = 0;
 38     int bytes_read = 0;
 39     char buffer[BUFFER_SIZE + 1];
 40 
 41     if (access(FIFO_NAME, F_OK) == -1)
 42     {
 43         res = mkfifo(FIFO_NAME, 0777);
 44         if (res != 0)
 45         {
 46             fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
 47             exit(EXIT_FAILURE);
 48         }
 49     }
 50 
 51     printf("Process %d opening FIFO O_WRONLY\n", getpid());
 52     pipe_fd = open(FIFO_NAME, open_mode);
 53     printf("Process %d result %d\n", getpid(), pipe_fd);
 54 
 55 
 56     if (pipe_fd != -1)
 57     {
 58 
 59         if ((fp_in = fopen(TESTF_IN, "r")) < 0)
 60         {
 61          fprintf(stderr, "Open input file failed:%s\n", TESTF_IN);
 62             exit(EXIT_FAILURE);
 63         }
 64 
 65         while ((bytes_read = fread(buffer, sizeof(char), OP_LEN, fp_in)))
 66         {
 67             printf("PRODUCE: %d, %s", bytes_read, buffer);
 68 
 69             res = write(pipe_fd, buffer, bytes_read);
 70             if (res == -1)
 71             {
 72                 fprintf(stderr, "Write error on pipe\n");
 73                 exit(EXIT_FAILURE);
 74             }    
 75             bytes_sent += res;        
 76 
 77             memset(buffer, 0, BUFFER_SIZE);
 78             if (feof(fp_in) != 0)
 79             {
 80                 printf("read over\n");
 81                 break;
 82             }
 83         }
 84 
 85         (void)close(pipe_fd);
 86         fclose(fp_in);
 87     }
 88     else
 89     {
 90         exit(EXIT_FAILURE);
 91     }
 92 
 93     return 0;
 94 }
 95 
 96 consumer.c
 97 
 98 /*
 99  * \File
100  * consumer.c
101  *
102  */
103 
104 
105 #include <unistd.h>
106 #include <stdlib.h>
107 #include <stdio.h>
108 #include <string.h>
109 #include <fcntl.h>
110 #include <limits.h>
111 #include <sys/types.h>
112 #include <sys/stat.h>
113 
114 
115 #define FIFO_NAME "test_fifo"
116 #define BUFFER_SIZE (512)
117 #define OP_LEN (100)
118 
119 int main(char argc, char* argv[])
120 {
121     int pipe_fd;
122     int res;
123     int open_mode = O_RDONLY;
124     char buffer[BUFFER_SIZE + 1];
125     int bytes_read = 0;
126 
127     memset(buffer, \0, sizeof(buffer));
128 
129     printf("Process %d opening FIFO O_RDONLY\n", getpid());
130     pipe_fd = open(FIFO_NAME, open_mode);
131     printf("Process %d result %d\n", getpid(), pipe_fd);
132 
133     if (pipe_fd != -1)
134     {
135         do
136         {
137             res = read(pipe_fd, buffer, OP_LEN);
138             printf("CONSUME: %d, %s\n", res, buffer);
139             bytes_read += res;
140 
141             memset(buffer, 0, BUFFER_SIZE);
142 
143             if (res < OP_LEN)
144             {
145                 printf("read over\n");
146                 break;
147             }
148         } while (res > 0);
149         (void)close(pipe_fd);
150     }
151     else
152     {
153         exit(EXIT_FAILURE);
154     }
155 
156     printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
157     exit(EXIT_SUCCESS);
158     return 0;
159 }
View Code

三.共享内存

  共享内存是三个IPC(Inter-Process Communication)允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在进行的进程之间传递数据的一种非常有效的方式,大多数的共享内存的实现,都把由不同进程之间共享的内存安排为同一段物理内存。共享内存是由IPC为进程创建一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接它们自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由malloc分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。共享内存并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。我们通常是用共享内存来提供对大块内存区域的有效访问,同时通过传递小消息来同步对该内存的访问。在第一个进程结束对共享内存的写操作之前,并无自动的机制可以阻止第二个进程开始对它进行读取。对共享内存访问的同步控制必须由程序员来负责。示例代码是一个典型的的消费者生产者模式,消费者将创建一个共享内存段,然后把写到它里面的数据显示出来。生产者将连接一个已有的共享内存端,并允许我们向其中输入数据。

1 //共享内存使用的函数
2 #include <sys/shm.h>
3 int shmget(key_t key, size_t size, int shmflg);
4 void *shmat(int shm_id, const void *shm_addr, int shmflg);
5 int shmdt(const void *shm_addr);
6 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
技术分享
  1 //shm_com.h
  2 #define TEXT_SZ 2048
  3 
  4 struct shared_use_st {
  5   int written_by_you;
  6   char some_text[TEXT_SZ];
  7 };
  8 
  9 //shm1.c消费者程序
 10 #include <unistd.h>
 11 #include <stdlib.h>
 12 #include <stdio.h>
 13 #include <string.h>
 14 
 15 #include <sys/shm.h>
 16 
 17 #include "shm_com.h"
 18 int main()
 19 {
 20   int running = 1;
 21   void *shared_memory = (void *)0;
 22   struct shared_use_st *shared_stuff;
 23   int shmid;
 24 
 25   srand((unsigned int)getpid());
 26   shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
 27 
 28   if (shmid == -1) {
 29     fprintf(stderr, "shmget failed\n");
 30     exit(EXIT_FAILURE);
 31   }
 32 shared_memory = shmat(shmid, (void *)0, 0);
 33 
 34   if (shared_memory == (void *)-1) {
 35     fprintf(stderr, "shmat failed\n");
 36     exit(EXIT_FAILURE);
 37   }
 38 
 39   printf("Memory attached at %X\n", (int)shared_memory);
 40 shared_stuff = (struct shared_use_st *)shared_memory;
 41   shared_stuff->written_by_you = 0;
 42 
 43   while(running) 
 44   {
 45     if (shared_stuff->written_by_you) 
 46     {
 47       printf("You wrote: %s", shared_stuff->some_text);
 48 
 49       sleep( rand() % 4 ); /* make the other process wait for us ! */
 50       shared_stuff->written_by_you = 0;
 51 
 52       if (strncmp(shared_stuff->some_text, “end”, 3) == 0) {
 53         running = 0;
 54       }
 55     }
 56   }
 57 if (shmdt(shared_memory) == -1) 
 58   {
 59     fprintf(stderr, "shmdt failed\n");
 60     exit(EXIT_FAILURE);
 61   }
 62 
 63   if (shmctl(shmid, IPC_RMID, 0) == -1) 
 64   {
 65     fprintf(stderr, "shmctl(IPC_RMID) failed\n");
 66     exit(EXIT_FAILURE);
 67   }
 68 
 69   exit(EXIT_SUCCESS);
 70 }
 71 
 72 //shm2.c生产者程序
 73 #include <unistd.h>
 74 #include <stdlib.h>
 75 #include <stdio.h>
 76 #include <string.h>
 77 
 78 #include <sys/shm.h>
 79 
 80 #include "shm_com.h"
 81 
 82 int main()
 83 {
 84   int running = 1;
 85   void *shared_memory = (void *)0;
 86   struct shared_use_st *shared_stuff;
 87   char buffer[BUFSIZ];
 88   int shmid;
 89 
 90   shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
 91   if (shmid == -1) 
 92   {
 93     fprintf(stderr, "shmget failed\n");
 94     exit(EXIT_FAILURE);
 95   }
 96 
 97   shared_memory = shmat(shmid, (void *)0, 0);
 98   if (shared_memory == (void *)-1) 
 99   {
100     fprintf(stderr, "shmat failed\n");
101     exit(EXIT_FAILURE);
102   }
103 
104   printf("Memory attached at %X\n", (int)shared_memory);
105 
106   shared_stuff = (struct shared_use_st *)shared_memory;
107   while(running) 
108   {
109     while(shared_stuff->written_by_you == 1) 
110     {
111       sleep(1);
112       printf("waiting for client...\n");
113     }
114     printf("Enter some text: ");
115     fgets(buffer, BUFSIZ, stdin);
116 
117     strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
118     shared_stuff->written_by_you = 1;
119 
120     if (strncmp(buffer, "end", 3) == 0) {
121       running = 0;
122     }
123   }
124 
125   if (shmdt(shared_memory) == -1) {
126     fprintf(stderr, "shmdt failed\n");
127     exit(EXIT_FAILURE);
128   }
129 
130   exit(EXIT_SUCCESS);
131 }
132 
133 /*运行程序
134 $ ./shm1 &
135 [1] 294
136 Memory attached at 40017000
137 $ ./shm2
138 Memory attached at 40017000
139 Enter some text: hello
140 You wrote: hello
141 waiting for client...
142 waiting for client...
143 Enter some text: 
144 You wrote: 
145 waiting for client...
146 waiting for client...
147 waiting for client...
148 Enter some text: end
149 You wrote: end
150 $*/
View Code

四.消息队列

  消息队列与命名管道类似,但少了打开和关闭管道方面的复杂性。但使用消息队列并未解决我们在使用命名管道时遇到的一些问题,如管道满时的阻塞问题。消息队列提供了一种在两个不相关进程间传递数据的简单有效的方法。与命名管道相比,消息队列的优势在于,它独立于发送和接收进程而存在,这消除了在同步命名管道的打开和关闭时可能产生的一些困难。消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。而且,每个数据块被认为含有一个类型,接收进程可以独立地接收含有不同类型值的数据块。好消息是,我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题。而且,我们可以用一些方法来提前查看紧急消息。坏消息是,与管道一样,每个数据块有一个最大长度的限制,而且,系统中所有队列所包含的全部数据块的总长度也有一个上限。Linux系统中有两个宏定义:MSGMAX, 以字节为单位,定义了一条消息的最大长度。MSGMNB, 以字节为单位,定义了一个队列的最大长度。示例程序msg1.c用于接收消息,msg2.c用于发送消息。允许两个程序都可以创建消息队列,但只有接收者在接收完最后一个消息后可以删除它。发送者程序通过msgget来创建一个消息队列,然后用msgsnd向队列中增加消息。接收者程序用msgget获得消息队列标识符,然后开始接收消息,直到接收到特殊文件end为止。然后它用msgctl来删除消息队列以完成清理工作。

1 //消息队列使用的函数
2 #include <sys/msg.h>
3 int msgctl(int msqid, int cmd, struct msqid_ds *buf);
4 int msgget(key_t key, int msgflg);
5 int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
6 int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);
技术分享
  1 #include <stdlib.h>
  2 #include <stdio.h>
  3 #include <string.h>
  4 #include <errno.h>
  5 #include <unistd.h>
  6 
  7 #include <sys/msg.h>
  8 
  9 
 10 struct my_msg_st {
 11   long int my_msg_type;
 12   char some_text[BUFSIZ];
 13 };
 14 
 15 
 16 int main()
 17 {
 18   int running = 1;
 19   int msgid;
 20   struct my_msg_st some_data;
 21   long int msg_to_receive = 0;
 22  //首先创建消息队列:
 23   msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
 24 
 25   if (msgid == -1) 
 26   {
 27     fprintf(stderr, "msgget failed with error: %d\n", errno);
 28     exit(EXIT_FAILURE);
 29   }
 30 //然后从队列中获取消息,直到遇见end消息为止。最后,删除消息队列:
 31   while(running) 
 32   {
 33     if (msgrcv(msgid, (void *)&some_data, BUFSIZ, msg_to_receive, 0) == -1) 
 34     {
 35       fprintf(stderr, "msgrcv failed with error: %d\n", errno);
 36       exit(EXIT_FAILURE);
 37     }
 38 
 39     printf("You wrote: %s", some_data.some_text);
 40     if (strncmp(some_data.some_text, "end", 3) == 0) 
 41     {
 42       running = 0;
 43     }
 44   }
 45 
 46   if (msgctl(msgid, IPC_RMID, 0) == -1) 
 47   {
 48     fprintf(stderr, “msgctl(IPC_RMID) failed\n”);
 49     exit(EXIT_FAILURE);
 50   }
 51   exit(EXIT_SUCCESS);
 52 }
 53 
 54 //通过调用msgsnd来发送用户输入的文本到消息队列中
 55 #include <stdlib.h>
 56 #include <stdio.h>
 57 #include <string.h>
 58 #include <errno.h>
 59 #include <unistd.h>
 60 
 61 #include <sys/msg.h>
 62 
 63 #define MAX_TEXT 512
 64 
 65 struct my_msg_st 
 66 {
 67   long int my_msg_type;
 68   char some_text[MAX_TEXT];
 69 };
 70 
 71 int main()
 72 {
 73   int running = 1;
 74   struct my_msg_st some_data;
 75   int msgid;
 76   char buffer[BUFSIZ];
 77   
 78   msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
 79 
 80   if (msgid == -1) 
 81   {
 82     fprintf(stderr, "msgget failed with error: %d\n", errno);
 83     exit(EXIT_FAILURE);
 84   }
 85 
 86   while(running) 
 87   {
 88     printf("Enter some text: ");
 89     fgets(buffer, BUFSIZ, stdin);
 90 
 91     some_data.my_msg_type = 1;
 92     strcpy(some_data.some_text, buffer);
 93 
 94     if (msgsnd(msgid, (void *)&some_data, MAX_TEXT, 0) == -1) 
 95     {
 96       fprintf(stderr, "msgsnd failed\n");
 97       exit(EXIT_FAILURE);
 98     }
 99 
100     if (strncmp(buffer, "end", 3) == 0) 
101     {
102       running = 0;
103     }
104   }
105   exit(EXIT_SUCCESS);
106 }
107 
108 /*
109 $ ./msg2
110 Enter some text: hello
111 Enter some text: How are you today?
112 Enter some text: end
113 $ ./msg1
114 You wrote: hello
115 You wrote: How are you today?
116 You wrote: end
117 $
118 */
View Code

五.信号量

  荷兰计算机科学家Edsger Dijkstra提出的信号量概念是在并发编程领域迈出的重要一步。信号量用于对临界资源的并发访问控制,使用PV操作把守代码中的临界区域。信号量是一个特殊的变量,它只取正数值,并且程序对其访问都是原子操作。信号量分成:二进制信号量, 取值只能是0和1;通用信号量,可以取多个正整数值。信号量只允许对它进行等待(wait)和发送信号(signal)这两种操作,P操作: 用于等待。V操作: 用于发送信号。示例程序中用两个不同字符的输出来表示进入和离开临界区域。如果程序启动时带有一个参数,它将在进入和退出临界区域时打印字符X; 而程序的其它运行实例将在进入和退出临界区域时打印字符O;因为在任一给定时刻,只能有一个进程可以进入临界区域,所以字符X和O应该是成对出现的.

  • P(sv)  如果 sv的值 > 0, 就给它减去1;如果 sv的值 == 零,就挂起该进程的执行.
  • V(sv)  如果有其它进程因等待sv而被挂起, 就让它恢复运行;如果没有进程因等待sv而被挂起,就给它加1
semaphore sv = 1;
loop forever {
  noncritical code section;

  P(sv);
  critical code section; 
  V(sv);

  noncritical code section;
}
1 //信号量函数定义
2 #include <sys/sem.h>
3 
4 int semget(key_t key, int num_sems, int sem_flags);
5 int semctl(int sem_id, int sem_num, int command, ...);
6 int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

 

技术分享
  1 #include <unistd.h>
  2 #include <stdlib.h>
  3 #include <stdio.h>
  4 
  5 #include <sys/sem.h>
  6 
  7 #include "semun.h"
  8 
  9 static int set_semvalue(void);
 10 static void del_semvalue(void);
 11 static int semaphore_p(void);
 12 static int semaphore_v(void);
 13 
 14 static int sem_id; // 信号量标识符
 15 
 16 int main(int argc, char *argv[])
 17 {
 18   int i;
 19   int pause_time;
 20   char op_char = ‘O’;
 21   
 22   srand((unsigned int)getpid());
 23   
 24   /* 创建信号量 */
 25   sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
 26   if (argc > 1) 
 27   {
 28     if (!set_semvalue()) 
 29     {
 30       fprintf(stderr, “Failed to initialize semaphore\n”);
 31       exit(EXIT_FAILURE);
 32     }
 33     op_char = ‘X’;
 34     sleep(2);
 35   }
 36 
 37 /* 接下来是一个循环,它进入和离开临界区10次。
 38 在每次循环的开始时,
 39 首先调用semaphore_p函数,
 40 它在程序进入临界区域时设置信号量以等待进入*/
 41 
 42   for(i = 0; i < 10; i++) 
 43   {
 44     if (!semaphore_p()) // P操作, 等待 
 45       exit(EXIT_FAILURE);
 46     
 47     /* 临界区域代码 */
 48     printf(“%c”, op_char);fflush(stdout);
 49     pause_time = rand() % 3;
 50     sleep(pause_time);
 51     printf(“%c”, op_char);fflush(stdout);
 52     /* 临界区域结束 */
 53 /* 在临界区域之后,
 54 调用semaphore_v来将信号量设置为可用,
 55 然后等待一段随机时间,
 56 再进入下一次循环。
 57 在整个循环语句执行完毕后,
 58 调用del_semvalue函数来清理信号量*/
 59     if (!semaphore_v()) // V操作,发送信号
 60       exit(EXIT_FAILURE);
 61 
 62     pause_time = rand() % 2;
 63     sleep(pause_time);
 64   } // end of for
 65 
 66   printf(“\n%d - finished\n”, getpid());
 67   if (argc > 1) 
 68   {
 69     sleep(10);
 70     del_semvalue(); // 清理信号量
 71   }
 72 
 73   exit(EXIT_SUCCESS);
 74 } // end of main
 75 
 76 /*函数set_semvalue通过调用semctl的command参数SETVAL,
 77 来初始化信号量。
 78 在使用信号量之前必须这样做。*/
 79 
 80 static int set_semvalue(void)
 81 {
 82   union semun sem_union;
 83   sem_union.val = 1;
 84 
 85   if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 
 86     return(0);
 87 
 88   return(1);
 89 }
 90 
 91 /*函数del_smvalue通过调用semctl的command参数IPC_RMID,
 92 来删除信号量ID。
 93 实际编程时一次要在执行结束前进行信号量删除,
 94 以防导致下次程序引用时出错。*/
 95 
 96 static void del_semvalue(void)
 97 {
 98   union semun sem_union;
 99   if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
100     fprintf(stderr, “Failed to delete semaphore\n”);
101 }
102 
103 (6)函数semaphore_p对信号做减1操作(等待):
104 
105 static int semaphore_p(void)
106 {
107   struct sembuf sem_b;
108   sem_b.sem_num = 0;
109   sem_b.sem_op = -1; /* P() */
110   sem_b.sem_flg = SEM_UNDO;
111 
112   if (semop(sem_id, &sem_b, 1) == -1) {
113     fprintf(stderr, “semaphore_p failed\n”);
114     return(0);
115   }
116   return(1);
117 }
118 
119 //函数semaphore_v将sembuf结构中的sem_op设置为1(释放):
120 
121 static int semaphore_v(void)
122 {
123   struct sembuf sem_b;
124   sem_b.sem_num = 0;
125   sem_b.sem_op = 1; /* V() */
126   sem_b.sem_flg = SEM_UNDO;
127 
128   if (semop(sem_id, &sem_b, 1) == -1) {
129     fprintf(stderr, “semaphore_v failed\n”);
130     return(0);
131   }
132 
133   return(1);
134 }
135 
136 
137 /*下面是两个程序调用实例时的一些样本输出:
138 $ cc sem1.c -o sem1
139 $ ./sem1 1 &
140 [1] 1082
141 
142 $ ./sem1
143 OOXXOOXXOOXXOOXXOOXXOOOOXXOOXXOOXXOOXXXX
144 1083 - finished
145 1082 - finished
146 $*/
View Code

 

五.信号

  信号提供了一种处理异步事件的方法。每个信号都有一个名字。这些名字都以三个字符SIG开头。在头文件<signal.h>中,这些信号都被定义为正整数(信号编号)。不存在编号为0的信号。(kill函数对信号编号0有特殊的应用)。信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量(例如errno)来判别是否出现了一个信号,而是必须告诉内核“在此信号出现时,请执行下列操作”。下面的实例是通过信号机制处理僵死进程,子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。

产生信号的方式:

  • 当用户按某些终端键时,引发终端产生的信号。
  • 硬件异常产生信号。
  • 进程调用kill(2)函数可将信号发送给另一个进程或进程组。(接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者必须是超级用户。)
  • 用户可用kill(1)命令将信号发送给其他进程。

可以要求内核在某个信号出现时按照下列三种方式之一进行处理,我们称之为信号的处理或者与信号相关的动作。

  • 忽略此信号。大多数信号都可使用这种方法进行处理,但是有两种信号决不能被忽略:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如除以0),则进程的运行行为是未定义的。
  • 捕捉信号。为了做到这一点,要通知内核在某种信号发生时调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。注意,不能捕捉SIGKILL和SIGSTOP信号。
  • 执行系统默认动作。注意,针对大多数信号的系统默认动作是终止
技术分享
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <errno.h>
 4 #include <stdlib.h>
 5 #include <signal.h>
 6 
 7 static void sig_child(int signo);
 8 
 9 int main()
10 {
11     pid_t pid;
12     //创建捕捉子进程退出信号
13     signal(SIGCHLD,sig_child);
14     pid = fork();
15     if (pid < 0)
16     {
17         perror("fork error:");
18         exit(1);
19     }
20     else if (pid == 0)
21     {
22         printf("I am child process,pid id %d.I am exiting.\n",getpid());
23         exit(0);
24     }
25     printf("I am father process.I will sleep two seconds\n");
26     //等待子进程先退出
27     sleep(2);
28     //输出进程信息
29     system("ps -o pid,ppid,state,tty,command");
30     printf("father process is exiting.\n");
31     return 0;
32 }
33 
34 static void sig_child(int signo)
35 {
36      pid_t        pid;
37      int        stat;
38      //处理僵尸进程
39      while ((pid = waitpid(-1, &stat, WNOHANG)) >0)
40             printf("child %d terminated.\n", pid);
41 }
View Code

小结

  1. 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  6. 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  7. 套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机间的进程通信

参考:

  • 进程与信号
  • 孤儿进程和僵死进程
  • Linux环境进程间通信(一):管道及有名管道 
  • linux进程间的通信(C): 命名管道

Linux进程间的通信