Cinq méthodes de communication entre les processus : 1. Pipe, qui est lent et a une capacité limitée. Seuls les processus parent et enfant peuvent communiquer ; 2. FIFO, qui peut communiquer entre n'importe quel processus, mais est lent 3. Message ; file d'attente, Une requête aléatoire de messages peut être réalisée, mais la capacité est limitée par le système ; 4. Les sémaphores ne peuvent pas transmettre de messages complexes et ne peuvent être utilisés que pour la synchronisation ; 5. La zone de mémoire partagée signifie que deux processus ou plus partagent une zone de stockage donnée ; .
L'environnement d'exploitation de ce tutoriel : système Windows 7, ordinateur Dell G3.
Cinq méthodes de communication entre les processus :
Pipeline, fait généralement référence à. le canal sans nom, qui est la forme la plus ancienne d'IPC dans les systèmes UNIX.
Il est semi-duplex (c'est-à-dire que les données ne peuvent circuler que dans une seule direction), avec une fin de lecture et d'écriture fixe. fin.
Il ne peut être utilisé que pour la communication entre des processus qui ont une relation (également entre des processus parent-enfant ou des processus frères et sœurs).
Il peut être considéré comme un fichier spécial, et des fonctions de lecture, d'écriture et autres ordinaires peuvent également être utilisées pour le lire et l'écrire. Mais ce n’est pas un fichier ordinaire, n’appartient à aucun autre système de fichiers et n’existe qu’en mémoire.
1 #include <unistd.h> 2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1</unistd.h>
Lorsqu'un pipeline est établi, il crée deux descripteurs de fichiers : fd[0]
pour la lecture Open, fd[1]
ouvert à l'écriture. Comme indiqué ci-dessous :
Pour fermer le canal, fermez simplement ces deux descripteurs de fichiers.
Les pipelines en un seul processus sont presque inutiles. Ainsi, généralement le processus qui appelle pipe appelle ensuite fork, créant ainsi un canal IPC entre le processus parent et le processus enfant. Comme le montre la figure ci-dessous :
Si le flux de données circule du processus parent vers le processus enfant, fermez la fin de lecture du processus parent (fd[0]
) et la fin d'écriture du processus enfant ( fd[1]
) ; à l'inverse, le flux de données peut s'effectuer du processus enfant vers le processus parent.
1 #include<stdio.h> 2 #include<unistd.h> 3 4 int main() 5 { 6 int fd[2]; // 两个文件描述符 7 pid_t pid; 8 char buff[20]; 9 10 if(pipe(fd) 0) // 父进程 16 { 17 close(fd[0]); // 关闭读端 18 write(fd[1], "hello world\n", 12); 19 } 20 else 21 { 22 close(fd[1]); // 关闭写端 23 read(fd[0], buff, 20); 24 printf("%s", buff); 25 } 26 27 return 0; 28 }</unistd.h></stdio.h>
FIFO, également connu sous le nom de tube nommé, est un type de fichier.
FIFO peut échanger des données entre des processus non liés, contrairement aux canaux sans nom.
FIFO est associé à un nom de chemin et il existe dans le système de fichiers en tant que fichier de périphérique spécial.
1 #include <sys> 2 // 返回值:成功返回0,出错返回-1 3 int mkfifo(const char *pathname, mode_t mode);</sys>
est le même que le mode dans la fonction open
. Une fois qu'un FIFO est créé, il peut être manipulé à l'aide des fonctions d'E/S de fichier normales.
Lors de l'ouverture d'un FIFO, la différence entre le fait que l'indicateur non bloquant (O_NONBLOCK
) soit défini :
Si O_NONBLOCK
n'est pas spécifié (par défaut) , ouvert en lecture seule Pour bloquer jusqu'à ce qu'un autre processus ouvre ce FIFO en écriture. De même, les blocs ouverts en écriture seule jusqu'à ce qu'un autre processus l'ouvre en lecture.
Si O_NONBLOCK
est spécifié, l'ouverture en lecture seule revient immédiatement. Une ouverture en écriture seule renverra -1 en cas d'erreur. Si aucun processus n'a ouvert le FIFO en lecture, son numéro d'erreur est défini sur ENXIO.
La méthode de communication du FIFO est similaire à l'utilisation de fichiers pour transmettre des données dans le processus, sauf que les fichiers de type FIFO ont également les caractéristiques des tuyaux. Lorsque les données sont lues, elles sont effacées du pipeline FIFO en même temps et sont « premier entré, premier sorti ». L'exemple suivant montre le processus d'utilisation de FIFO pour IPC :
write_fifo.c
1 #include<stdio.h> 2 #include<stdlib.h> // exit 3 #include<fcntl.h> // O_WRONLY 4 #include<sys> 5 #include<time.h> // time 6 7 int main() 8 { 9 int fd; 10 int n, i; 11 char buf[1024]; 12 time_t tp; 13 14 printf("I am %d process.\n", getpid()); // 说明进程ID 15 16 if((fd = open("fifo1", O_WRONLY)) <p>read_fifo.c</p> <pre class="brush:php;toolbar:false"> 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<errno.h> 4 #include<fcntl.h> 5 #include<sys> 6 7 int main() 8 { 9 int fd; 10 int len; 11 char buf[1024]; 12 13 if(mkfifo("fifo1", 0666) 0) // 读取FIFO管道 23 printf("Read message: %s", buf); 24 25 close(fd); // 关闭FIFO文件 26 return 0; 27 }</sys></fcntl.h></errno.h></stdlib.h></stdio.h>
Utilisez gcc pour compiler et exécuter les deux terminaux en deux ci-dessus ., vous pouvez voir le résultat comme suit :
1 [cheesezh@localhost]$ ./write_fifo 2 I am 5954 process. 3 Send message: Process 5954's time is Mon Apr 20 12:37:28 2015 4 Send message: Process 5954's time is Mon Apr 20 12:37:29 2015 5 Send message: Process 5954's time is Mon Apr 20 12:37:30 2015 6 Send message: Process 5954's time is Mon Apr 20 12:37:31 2015 7 Send message: Process 5954's time is Mon Apr 20 12:37:32 2015 8 Send message: Process 5954's time is Mon Apr 20 12:37:33 2015 9 Send message: Process 5954's time is Mon Apr 20 12:37:34 2015 10 Send message: Process 5954's time is Mon Apr 20 12:37:35 2015 11 Send message: Process 5954's time is Mon Apr 20 12:37:36 2015 12 Send message: Process 5954's time is Mon Apr 20 12:37:37 2015
1 [cheesezh@localhost]$ ./read_fifo 2 Read message: Process 5954's time is Mon Apr 20 12:37:28 2015 3 Read message: Process 5954's time is Mon Apr 20 12:37:29 2015 4 Read message: Process 5954's time is Mon Apr 20 12:37:30 2015 5 Read message: Process 5954's time is Mon Apr 20 12:37:31 2015 6 Read message: Process 5954's time is Mon Apr 20 12:37:32 2015 7 Read message: Process 5954's time is Mon Apr 20 12:37:33 2015 8 Read message: Process 5954's time is Mon Apr 20 12:37:34 2015 9 Read message: Process 5954's time is Mon Apr 20 12:37:35 2015 10 Read message: Process 5954's time is Mon Apr 20 12:37:36 2015 11 Read message: Process 5954's time is Mon Apr 20 12:37:37 2015
L'exemple ci-dessus peut être étendu à une instance de communication de processus client-serveur write_fifo
fonctionne comme un client et peut ouvrir plusieurs clients sur un serveur. . L'envoi d'informations sur la demande, read_fifo
est similaire à un serveur. Il surveille la fin de lecture du FIFO en temps opportun, lorsqu'il y a des données, il les lit et les traite. Cependant, un problème clé est que chaque client doit le faire. connaître à l'avance le FIFO fourni par le serveur, la figure suivante montre cet arrangement :
La file d'attente de messages est une liste chaînée de messages stockés dans le noyau. Une file d'attente de messages est identifiée par un identifiant (ID de file d'attente).
La file d'attente des messages est orientée enregistrement et les messages qu'elle contient ont un format et une priorité spécifiques.
La file d'attente des messages est indépendante des processus d'envoi et de réception. Lorsque le processus se termine, la file d'attente des messages et son contenu ne sont pas supprimés.
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
1 #include <sys> 2 // 创建或打开消息队列:成功返回队列ID,失败返回-1 3 int msgget(key_t key, int flag); 4 // 添加消息:成功返回0,失败返回-1 5 int msgsnd(int msqid, const void *ptr, size_t size, int flag); 6 // 读取消息:成功返回消息数据的长度,失败返回-1 7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag); 8 // 控制消息队列:成功返回0,失败返回-1 9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);</sys>
在以下两种情况下,msgget
将创建一个新的消息队列:
IPC_CREAT
标志位。IPC_PRIVATE
。函数msgrcv
在读取消息队列时,type参数有下面几种情况:
type == 0
,返回队列中的第一个消息;type > 0
,返回队列中消息类型为 type 的第一个消息;type ,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)
下面写了一个简单的使用消息队列进行IPC的例子,服务端程序一直在等待特定类型的消息,当收到该类型的消息以后,发送另一种特定类型的消息作为反馈,客户端读取该反馈并打印出来。
msg_server.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys> 4 5 // 用于创建一个唯一的key 6 #define MSG_FILE "/etc/passwd" 7 8 // 消息结构 9 struct msg_form { 10 long mtype; 11 char mtext[256]; 12 }; 13 14 int main() 15 { 16 int msqid; 17 key_t key; 18 struct msg_form msg; 19 20 // 获取key值 21 if((key = ftok(MSG_FILE,'z')) <p>msg_client.c</p> <pre class="brush:php;toolbar:false"> 1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys> 4 5 // 用于创建一个唯一的key 6 #define MSG_FILE "/etc/passwd" 7 8 // 消息结构 9 struct msg_form { 10 long mtype; 11 char mtext[256]; 12 }; 13 14 int main() 15 { 16 int msqid; 17 key_t key; 18 struct msg_form msg; 19 20 // 获取key值 21 if ((key = ftok(MSG_FILE, 'z')) <h2 id="四、信号量"><strong>四、信号量</strong></h2> <p>信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。</p> <h3 id="1、特点-2">1、特点</h3> <ol> <li><p>信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。</p></li> <li><p>信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。</p></li> <li><p>每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。</p></li> <li><p>支持信号量组。</p></li> </ol> <h3 id="2、原型-2">2、原型</h3> <p>最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。</p> <p>Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。</p> <pre class="brush:php;toolbar:false">1 #include <sys> 2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1 3 int semget(key_t key, int num_sems, int sem_flags); 4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1 5 int semop(int semid, struct sembuf semoparray[], size_t numops); 6 // 控制信号量的相关信息 7 int semctl(int semid, int sem_num, int cmd, ...);</sys>
当semget
创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems
),通常为1; 如果是引用一个现有的集合,则将num_sems
指定为 0 。
在semop
函数中,sembuf
结构的定义如下:
1 struct sembuf 2 { 3 short sem_num; // 信号量组中对应的序号,0~sem_nums-1 4 short sem_op; // 信号量值在一次操作中的改变量 5 short sem_flg; // IPC_NOWAIT, SEM_UNDO 6 }
其中 sem_op 是一次操作中的信号量的改变量:
若sem_op > 0
,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则换行它们。
若sem_op ,请求 sem_op 的绝对值的资源。
sem_flg
有关。IPC_NOWAIT
,则semop函数出错返回EAGAIN
。IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:若sem_op == 0
,进程阻塞直到信号量的相应值为0:
sem_flg
决定函数动作:IPC_NOWAIT
,则出错返回EAGAIN
。IPC_NOWAIT
,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:在semctl
函数中的命令有多种,这里就说两个常用的:
SETVAL
:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。IPC_RMID
:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys> 4 5 // 联合体,用于semctl初始化 6 union semun 7 { 8 int val; /*for SETVAL*/ 9 struct semid_ds *buf; 10 unsigned short *array; 11 }; 12 13 // 初始化信号量 14 int init_sem(int sem_id, int value) 15 { 16 union semun tmp; 17 tmp.val = value; 18 if(semctl(sem_id, 0, SETVAL, tmp) == -1) 19 { 20 perror("Init Semaphore Error"); 21 return -1; 22 } 23 return 0; 24 } 25 26 // P操作: 27 // 若信号量值为1,获取资源并将信号量值-1 28 // 若信号量值为0,进程挂起等待 29 int sem_p(int sem_id) 30 { 31 struct sembuf sbuf; 32 sbuf.sem_num = 0; /*序号*/ 33 sbuf.sem_op = -1; /*P操作*/ 34 sbuf.sem_flg = SEM_UNDO; 35 36 if(semop(sem_id, &sbuf, 1) == -1) 37 { 38 perror("P operation Error"); 39 return -1; 40 } 41 return 0; 42 } 43 44 // V操作: 45 // 释放资源并将信号量值+1 46 // 如果有进程正在挂起等待,则唤醒它们 47 int sem_v(int sem_id) 48 { 49 struct sembuf sbuf; 50 sbuf.sem_num = 0; /*序号*/ 51 sbuf.sem_op = 1; /*V操作*/ 52 sbuf.sem_flg = SEM_UNDO; 53 54 if(semop(sem_id, &sbuf, 1) == -1) 55 { 56 perror("V operation Error"); 57 return -1; 58 } 59 return 0; 60 } 61 62 // 删除信号量集 63 int del_sem(int sem_id) 64 { 65 union semun tmp; 66 if(semctl(sem_id, 0, IPC_RMID, tmp) == -1) 67 { 68 perror("Delete Semaphore Error"); 69 return -1; 70 } 71 return 0; 72 } 73 74 75 int main() 76 { 77 int sem_id; // 信号量集ID 78 key_t key; 79 pid_t pid; 80 81 // 获取key值 82 if((key = ftok(".", 'z')) <p>上面的例子如果不加信号量,则父进程会先执行完毕。这里加了信号量让父进程等待子进程执行完以后再执行。</p> <h2 id="五、共享内存"><strong>五、共享内存</strong></h2> <p>共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。</p> <h3 id="1、特点-3">1、特点</h3> <ol> <li><p>共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。</p></li> <li><p>因为多个进程可以同时操作,所以需要进行同步。</p></li> <li><p>信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。</p></li> </ol> <h3 id="2、原型-3">2、原型</h3> <pre class="brush:php;toolbar:false">1 #include <sys> 2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1 3 int shmget(key_t key, size_t size, int flag); 4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1 5 void *shmat(int shm_id, const void *addr, int flag); 6 // 断开与共享内存的连接:成功返回0,失败返回-1 7 int shmdt(void *addr); 8 // 控制共享内存的相关信息:成功返回0,失败返回-1 9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);</sys>
当用shmget
函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。
当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat
函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。
shmdt
函数是用来断开shmat
建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。
shmctl
函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID
(从系统中删除该共享内存)。
下面这个例子,使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。
server.c
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys> // shared memory 4 #include<sys> // semaphore 5 #include<sys> // message queue 6 #include<string.h> // memcpy 7 8 // 消息队列结构 9 struct msg_form { 10 long mtype; 11 char mtext; 12 }; 13 14 // 联合体,用于semctl初始化 15 union semun 16 { 17 int val; /*for SETVAL*/ 18 struct semid_ds *buf; 19 unsigned short *array; 20 }; 21 22 // 初始化信号量 23 int init_sem(int sem_id, int value) 24 { 25 union semun tmp; 26 tmp.val = value; 27 if(semctl(sem_id, 0, SETVAL, tmp) == -1) 28 { 29 perror("Init Semaphore Error"); 30 return -1; 31 } 32 return 0; 33 } 34 35 // P操作: 36 // 若信号量值为1,获取资源并将信号量值-1 37 // 若信号量值为0,进程挂起等待 38 int sem_p(int sem_id) 39 { 40 struct sembuf sbuf; 41 sbuf.sem_num = 0; /*序号*/ 42 sbuf.sem_op = -1; /*P操作*/ 43 sbuf.sem_flg = SEM_UNDO; 44 45 if(semop(sem_id, &sbuf, 1) == -1) 46 { 47 perror("P operation Error"); 48 return -1; 49 } 50 return 0; 51 } 52 53 // V操作: 54 // 释放资源并将信号量值+1 55 // 如果有进程正在挂起等待,则唤醒它们 56 int sem_v(int sem_id) 57 { 58 struct sembuf sbuf; 59 sbuf.sem_num = 0; /*序号*/ 60 sbuf.sem_op = 1; /*V操作*/ 61 sbuf.sem_flg = SEM_UNDO; 62 63 if(semop(sem_id, &sbuf, 1) == -1) 64 { 65 perror("V operation Error"); 66 return -1; 67 } 68 return 0; 69 } 70 71 // 删除信号量集 72 int del_sem(int sem_id) 73 { 74 union semun tmp; 75 if(semctl(sem_id, 0, IPC_RMID, tmp) == -1) 76 { 77 perror("Delete Semaphore Error"); 78 return -1; 79 } 80 return 0; 81 } 82 83 // 创建一个信号量集 84 int creat_sem(key_t key) 85 { 86 int sem_id; 87 if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1) 88 { 89 perror("semget error"); 90 exit(-1); 91 } 92 init_sem(sem_id, 1); /*初值设为1资源未占用*/ 93 return sem_id; 94 } 95 96 97 int main() 98 { 99 key_t key; 100 int shmid, semid, msqid; 101 char *shm; 102 char data[] = "this is server"; 103 struct shmid_ds buf1; /*用于删除共享内存*/ 104 struct msqid_ds buf2; /*用于删除消息队列*/ 105 struct msg_form msg; /*消息队列用于通知对方更新了共享内存*/ 106 107 // 获取key值 108 if((key = ftok(".", 'z')) <p>client.c</p> <pre class="brush:php;toolbar:false"> 1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys> // shared memory 4 #include<sys> // semaphore 5 #include<sys> // message queue 6 #include<string.h> // memcpy 7 8 // 消息队列结构 9 struct msg_form { 10 long mtype; 11 char mtext; 12 }; 13 14 // 联合体,用于semctl初始化 15 union semun 16 { 17 int val; /*for SETVAL*/ 18 struct semid_ds *buf; 19 unsigned short *array; 20 }; 21 22 // P操作: 23 // 若信号量值为1,获取资源并将信号量值-1 24 // 若信号量值为0,进程挂起等待 25 int sem_p(int sem_id) 26 { 27 struct sembuf sbuf; 28 sbuf.sem_num = 0; /*序号*/ 29 sbuf.sem_op = -1; /*P操作*/ 30 sbuf.sem_flg = SEM_UNDO; 31 32 if(semop(sem_id, &sbuf, 1) == -1) 33 { 34 perror("P operation Error"); 35 return -1; 36 } 37 return 0; 38 } 39 40 // V操作: 41 // 释放资源并将信号量值+1 42 // 如果有进程正在挂起等待,则唤醒它们 43 int sem_v(int sem_id) 44 { 45 struct sembuf sbuf; 46 sbuf.sem_num = 0; /*序号*/ 47 sbuf.sem_op = 1; /*V操作*/ 48 sbuf.sem_flg = SEM_UNDO; 49 50 if(semop(sem_id, &sbuf, 1) == -1) 51 { 52 perror("V operation Error"); 53 return -1; 54 } 55 return 0; 56 } 57 58 59 int main() 60 { 61 key_t key; 62 int shmid, semid, msqid; 63 char *shm; 64 struct msg_form msg; 65 int flag = 1; /*while循环条件*/ 66 67 // 获取key值 68 if((key = ftok(".", 'z')) <p>注意:当<code>scanf()</code>输入字符或字符串时,缓冲区中遗留下了<code>\n</code>,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持<code>fflush(stdin)</code>(它只是标准C的扩展),所以我们使用了替代方案:</p> <pre class="brush:php;toolbar:false">1 while((c=getchar())!='\n' && c!=EOF);
1.管道:速度慢,容量有限,只有父子进程能通讯
2.FIFO:任何进程间都能通讯,但速度慢
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
相关免费学习推荐:php编程(视频)
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!