Linux进程间的通信
Posted wxquare的学习笔记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 }
二.命名管道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 }
三.共享内存
共享内存是三个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 } Linux入门进程间的通信