1. 进程通信的概念
在同一台pc机上的两个进程通信是单机通信,不同的pc机上的两个进程通信叫多机通信。IPC(interprocess communication)也就是进程通信。
简单的来说就是两个进程之间能够互通有无。可以参考博文: https://blog.csdn.net/lyn_00/article/details/84789508
进程间通信使用的工具:
精彩博文推荐: https://blog.csdn.net/wh_sjc/article/details/70283843
2. 管道
管道(一般指无名管道)是半双工的,数据只能单向流通有固定的读端和写端,并且只能用于父子进程或者兄弟进程,它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。要关闭管道只需将这两个文件描述符关闭即可。
传一个fd数组进去,会创建两个文件描述符,fd[0]是读端,fd[1]是写端。管道存在于内核
当读的时候发现管道里没有数据的时候就会阻塞,有数据的时候才会停止阻塞。
3. FIFO
FIFO称为有名管道。是一个特殊的文件存在于磁盘中,打开的方式有两种,阻塞和非阻塞,默认打开是以阻塞的方式打开,此时当一个进程以只读的形式打开FIFO,就会阻塞,等待某个进程以只写的形式打开FIFO。
FIFO存在于磁盘,并且不局限于有亲缘关系的进程,毫无关系的两个进程和可以通过FIFO进行通信,只是管道是半双工。
使用 mkfifo(filepath,mode)——filepath是文件路径,mode同creat的mode相同。
代码范例:
// read
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main() {
int fd;
char *buf = (char *)malloc(128);
if (mkfifo("fifo1", 0600) == -1) {
perror("mkfifo error");
return -1;
}
fd = open("fifo1", O_RDONLY);
if (fd == -1) {
perror("open error");
return -1;
}
while (1) {
sleep(1);
read(fd, buf, 128);
printf("%s\n", buf);
}
return 0;
}
// write
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd;
int i;
fd = open("fifo1", O_WRONLY);
if (fd == -1) {
printf("open error\n");
return -1;
}
while (1) {
write(fd, "hello world", 12);
sleep(1);
}
return 0;
}
4. 消息队列
消息队列是面向记录的,消息队列位于内核,是一个链表,内核中有多个消息队列并且有单独的队列号来标识他们。
消息队列存在于内核,进程读消息并不会使消息队列中的消息消除,并且可以读队列的第一个和按类型来读取消息。
消息队列API:
1 #include <sys/msg.h>
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);
10 // 产生key值,成功返回key值,失败返回-1
11 key_t ftok(const char *pathname, int proj_id);
在以下两种情况下,msgget将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
key参数为IPC_PRIVATE。
函数msgrcv在读取消息队列时,type参数有下面几种情况:
type == 0,返回队列中的第一个消息;
type > 0,返回队列中消息类型为 type 的第一个消息;
type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。(其他的参数解释,请自行Google之)
代码范例:使用消息队列读写消息
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
struct Msg {
long m_type;
char m_text[128];
};
int main() {
key_t key = ftok(".",1);
int msgid;
int cnt = 0;
struct Msg message;
msgid = msgget(key, IPC_CREAT | 0777);
if (msgid == -1) {
perror("msgget error");
exit(1);
}
pid_t pid = fork();
if (pid > 0) {
while (1) {
sleep(1);
message.m_type = 666;
strcpy(message.m_text, "message form father");
msgsnd(msgid, &message, sizeof(struct Msg), 0);
msgrcv(msgid,&message,sizeof(message),777,0);
printf("father recive msg:%s\n", message.m_text);
cnt++;
if(cnt == 5){
break;
}
}
} else if (pid == 0) {
int n_rcv = 0;
while (1) {
sleep(1);
n_rcv = msgrcv(msgid, &message, sizeof(message), 666, 0);
printf("child recive msg:%s,n_rcv:%d\n", message.m_text, n_rcv);
message.m_type = 777;
strcpy(message.m_text, "message form child");
msgsnd(msgid, &message, sizeof(struct Msg), 0);
if(cnt == 5){
break;
}
cnt++;
}
}
msgctl(msgid,IPC_RMID,NULL);
return 0;
}
5. 共享内存
共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
共享内存在内存当中开辟了一块公共的内存,A和B进程都能访问到这个内存,并且像操作普通内存一样操作这个内存区域。
1、特点
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2.原型
1 #include <sys/shm.h>
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);
10 // 产生key值,成功返回key值,失败返回-1
11 key_t ftok(const char *pathname, int proj_id);
步骤 :
- 开辟共享内存——shmget
- 共享内存映射在进程——shmat
- 读写数据
- 断开连接——shmdt
- 删除共享内存——shmctl
因为使用的是同一块内存,所以不支持原子操作,即多个进程同时写就会覆盖掉数据,所以要规范,当某个进程写的时候,其他进程等待写的进程写完之后再读或者写。
代码范例:使用共享内存发送数据:
// IPC_SHM_W.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main(){
key_t key;
int shmid;
char *shmaddr;
key=ftok(".",1);
if(key == -1){
perror("ftok error");
return -1;
}
shmid = shmget(key,1024 * 4,IPC_CREAT|0777);
if(shmid == -1){
perror("shmget error");
return -1;
}
shmaddr=shmat(shmid,0,0);
strcpy(shmaddr,"this message from write");
sleep(5);
if(shmdt(shmaddr) == -1){
perror("shmdt error");
return -1;
}
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
// IPC_SHM_R.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
int main(){
key_t key;
int shmid;
char *shmaddr;
char buf[1024];
key=ftok(".",1);
if(key == -1){
perror("ftok error");
return -1;
}
shmid = shmget(key,1024 * 4,IPC_CREAT|0777);
if(shmid == -1){
perror("shmget error");
return -1;
}
shmaddr=shmat(shmid,0,0);
strcpy(buf,shmaddr);
printf("%s\n",buf);
if(shmdt(shmaddr) == -1){
perror("shmdt error");
return -1;
}
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
5. 信号
精彩博文: https://blog.csdn.net/zb1593496558/article/details/80280346
https://www.jianshu.com/p/f445bfeea40a
#include <signal.h>
// 信号处理函数
typedef void (*sighandler_t)(int);
// 注册信号处理函数
sighandler_t signal(int signum, sighandler_t handler);
例:
#include<signal.h>
#include<stdio.h>
#include <unistd.h>
//typedef void (*sighandler_t)(int);
void handler(int signum)
{
if(signum == SIGIO)
printf("SIGIO signal: %d\n", signum);
else if(signum == SIGUSR1)
printf("SIGUSR1 signal: %d\n", signum);
else
printf("error\n");
}
int main(void)
{
//sighandler_t signal(int signum, sighandler_t handler);
signal(SIGIO, handler);
signal(SIGUSR1, handler);
printf("%d %d\n", SIGIO, SIGUSR1);
for(;;)
{
sleep(10000);
}
return 0;
}
// 该代码为进程注册了两次信号处理函数,当进程收到SIGIO,SIGUSR1信号的时候都会调用handler函数而不是系统调用,处理函数改为SIG_IGN宏可以忽略该信号,但是有些信号无法忽略。
#include <sys/types.h>
#include <signal.h>
// 发送信号
int kill(pid_t pid, int sig);
以上两种(signal,kill)无法使信号携带消息,而(sigsction,sigqueue)可以携带消息。
原型:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
sigaction:不需要备份原来信号的配置就把oldact配置成NULL,并且在配置act结构体的时候,sa_flags要配置成SA_SIGINFO才能接收数据。并且要配置结构体的sa_sigaction为处理函数。该处理函数的第一个参数为信号编号,第二个参数为携带信息的结构体,存放着许多信息。第三个参数为一个指针,该指针用于判断有无数据,为空则无数据。
sigqueue:使用的是共用体,所以只能发送一种类型的数据,否则会段错误。
代码范例,使用信号传递整形值:
// signal_sigqueue.c
1 #include <signal.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 int main(int argc,char **argv){
6 pid_t pid;
7 int signum;
8 union sigval value;
9 value.sival_int = 100;
10 signum = atoi(argv[1]);
11 pid = atoi(argv[2]);
12
13 sigqueue(pid,signum,value);
14 printf("send signal success\n");
15
16 }
// signal_sigaction.c
1 #include <stdio.h>
2 #include <signal.h>
3
4 void handle(int signum, siginfo_t *info,void *context){
5 ....
6 printf("signum is %d\n",signum);
7 if(context!=NULL){
8 printf("value is %d\n",info->si_value.sival_int);
9 }
10
11 }
12
13 int main(){
14 struct sigaction act;
15 act.sa_flags = SA_SIGINFO;
16 act.sa_sigaction = handle;
17 printf("pid is %d\n",getpid());
18
19 sigaction(SIGINT,&act,NULL);
20 while(1);
21
22 return 0;
23 }
6. 信号量
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。可以理解成房间的钥匙,一个进程拿走了钥匙,也就是P操作,其他进程就进不了房间了,要把钥匙放回去,其他进程才可以进到房间。
1、特点
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。P(拿锁),V(释放锁)
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
2、原型
最简单的信号量是只能取 0(不可进入) 和 1(可进入) 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。
Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。
1 #include <sys/sem.h>
2 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
第一个参数是键,第二个参数是信号量集中要有多少个信号量,第三个参数同creat的mode(IPC_CREAT|0777)
3 int semget(key_t key, int num_sems, int sem_flags);
4 // 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1,第一个参数是信号量集的id,第二个参数是一个数组,第三个参数是代表第二个参数的数组有多少个成员
5 int semop(int semid, struct sembuf semoparray[], size_t numops);
6 // 控制信号量的相关信息,第一个参数是信号量集中的id,第二个参数是信号量集中的第几个信号量,第三个参数是相关操作,请参考man手册或其他,第四个参数要传一个共用体
7 int semctl(int semid, int sem_num, int cmd, ...);
要使用信号量必须定义共用体:
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 }
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO(Linux-specific) */
};
范例:共享内存和信号量同时使用:
// shmandsem_boy.c
1 #include <stdio.h>
2 #include <sys/ipc.h>
3 #include <sys/shm.h>
4 #include <sys/types.h>
5 #include <sys/sem.h>
6 #include <string.h>
7
8 union semun {
9 int val; /* Value for SETVAL */
10 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
11 unsigned short *array; /* Array for GETALL, SETALL */
12 struct seminfo *__buf; /* Buffer for IPC_INFO
13 (Linux-specific) */
14 };
15 //P操作 拿到锁
16 void Psem(int semid){
17 struct sembuf set;
18 set.sem_num = 0;
19 set.sem_op = -1;
20 set.sem_flg = SEM_UNDO;
21 semop(semid,&set,1);
22 }
23 // V操作,释放锁
24 void Vsem(int semid){
25 struct sembuf set;
26 set.sem_num = 0;
27 set.sem_op = 1;
28 set.sem_flg = SEM_UNDO;
29 semop(semid,&set,1);
30 }
31
32 int main() {
33 key_t key_shm;
34 key_t key_sem;
35 int shmid;
36 int semid;
37 char *shmaddr;
38
39 key_shm = ftok(".", 1);
40 key_sem = ftok(".", 2);
41
42 shmid = shmget(key_shm, 1024 * 4, IPC_CREAT | 0777);
43 semid = semget(key_sem, 1, IPC_CREAT | 0777);
44
45 union semun set;
46 set.val = 1; // 为1 是有 锁,为0是无
47 shmaddr = (char *)shmat(shmid, 0, 0);
48 semctl(semid, 0, SETVAL,set);
49 while (1) {
50 Psem(semid);
51 strcpy(shmaddr,"this msg form boy");
52 Vsem(semid);
53 sleep(1);
54 printf("%s\n",shmaddr);
55 }
56 ....
57 shmdt((void *)shmaddr);
58
59 shmctl(shmid,IPC_RMID,NULL);
60 semctl(semid,0,IPC_RMID,NULL);
61 return 0;
62 }
// shmandsem_girl.c
1 #include <stdio.h>
2 #include <sys/ipc.h>
3 #include <sys/shm.h>
4 #include <sys/types.h>
5 #include <sys/sem.h>
6 #include <string.h>
7
8 union semun {
9 int val; /* Value for SETVAL */
10 struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
11 unsigned short *array; /* Array for GETALL, SETALL */
12 struct seminfo *__buf; /* Buffer for IPC_INFO
13 (Linux-specific) */
14 };
15
16 void Psem(int semid){
17 struct sembuf set;
18 set.sem_num = 0;
19 set.sem_op = -1;
20 set.sem_flg = SEM_UNDO;
21 semop(semid,&set,1);
22 }
23
24 void Vsem(int semid){
25 struct sembuf set;
26 set.sem_num = 0;
27 set.sem_op = 1;
28 set.sem_flg = SEM_UNDO;
29 semop(semid,&set,1);
30 }
31
32 int main() {
33 key_t key_shm;
34 key_t key_sem;
35 int shmid;
36 int semid;
37 char *shmaddr;
38
39 key_shm = ftok(".", 1);
40 key_sem = ftok(".", 2);
41
42 shmid = shmget(key_shm, 1024 * 4, IPC_CREAT | 0777);
43 semid = semget(key_sem, 1, IPC_CREAT | 0777);
44
45 union semun set;
46 set.val = 1;
47 shmaddr = (char *)shmat(shmid, 0, 0);
48 semctl(semid, 0, SETVAL,set);
49 while (1) {
50 Psem(semid);
51 strcpy(shmaddr,"this msg form girl");
52 Vsem(semid);
53 sleep(1);
54 printf("%s\n",shmaddr);
55 }
56
57 shmdt((void *)shmaddr);
58
59 shmctl(shmid,IPC_RMID,NULL);
60 semctl(semid,0,IPC_RMID,NULL);
61 return 0;
62 }