什么是进程间通信?
两个进程可以同时往公共内存区(管道)存或发数据这样的全双工通信是真正意义上的通信。
消息队列包括:无名管道(父子进程通信),命名管道(FIFO),消息队列,共享内存,信号,信号量。
1、无名管道
管道通常指无名管道
特点:
(1)它是半双工的,数据同一时间只能在一个方向上流动。
(2)只能用于父子进程间通信。
(3)它可以看成是特殊的文件,对于它的读写,也可以使用write,read等函数;但它不属于文件,不存在于其他任何文件系统,只存在于内存中。
原型:
#include <unistd.h>
int pipe(int pipefd[2]);
pipe是创建一个管道,当一个管道建立时,它会创建两个参数,fd[0]为读而打开,fd[1]为写而打开;要关闭管道,只需要关闭这两个文件描述符即可。
0-1,读写。
1.创建无名管道:
第一步:创建无名管道
第二步:关闭读端,判断父进程,父进程给写端写入hello,world。
第三步:关闭写端,判断子进程,子进程把hello,world读出来。
并且父亲写完要等待子进程读完才结束wait().
子进程的读取是阻塞的,只有读到了数据才会往下进行,否则一直卡在这里。
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int pid;
char readbuf[128];
int flag = pipe(fd);
if(flag == -1)
{
printf("creat pipe failed\n");
}
pid = fork();//创建子进程
if(pid>0)
{
sleep(3);
printf("this is father\n");
close(fd[0]);
write(fd[1],"hello world",strlen("hello world"));
wait();//不能让父进程先结束,让父进程在这边等着
}
if(pid<0)
{
printf("creat child failed\n");
}
else if(pid == 0)
{
printf("this is child\n");
close(fd[1]);
memset(readbuf,0,128);
read(fd[0],readbuf,128);//把fd[0]端的数据读到readbuf里面,如果fd[0]里面没数据,read就会阻塞
printf("read context from father is %s\n",readbuf);
exit(0);//不加exit(0),会读不出来数据
}
return 0;
}
当程序运行到子进程处,readbuf里面数据为空,这时read堵塞,父进程往fd[1]里面写数据,写入之后,父进程先不结束而是等待子进程运行之后再结束。
2、FIFO(命名管道)
FIFO也成命名管道,
原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
mkfifo创建成功返回0,创建失败返回-1;一旦创建成功fifo,就可用一般文件操作它。
char *pathname:管道名字,mode:管道权限,0600可读可写
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main()
{
if((mkfifo("./file",0600) == -1) && errno != EEXIST)//如果管道创建失败且失败的原因不是已存在
{
printf("make fifo failed\n");
perror("why");
}
return 0;
}
当open一个fifo时,是否设置非阻塞标志O_NONBLOCK的区别:
若没有指定(默认有阻塞),只读open阻塞到其他进程为写而打开次fifo,只写open要阻塞到其他进程为读而打开次fifo。
即FIFO要读取的时候,只有当其他进程要给FIFO写入数据时,才会顺利往下进行。
例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
open("./file",O_RDONLY);
printf("read success\n");
return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
open("./file",O_WRONLY);
printf("write success\n");
return 0;
}
两者即时通信才能读写成功,两个进程间通信用fifo实现
//写入端代码
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char *writebuf = "hello world";
int fd = open("./file",O_WRONLY);
write(fd,writebuf,strlen(writebuf));
printf("write success\n");
close(fd);
return 0;
}
//读取端代码
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char readbuf[32];
if((mkfifo("./file",0600) == -1) && errno != EEXIST)//如果管道创建失败且
失败的原因不是已存在
{
printf("make fifo failed\n");
perror("why");
}
int fd = open("./file",O_RDONLY);
read(fd,readbuf,32);
printf("read success\n");
printf("read context:%s\n",readbuf);
return 0;
}
读的时候让fifo阻塞,只有给他写入数据时,才继续运行。
3.消息队列的通信原理
1 #include <sys/msg.h>
2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
3 int msgget(key_t key, int flag);
//key 队列索引值,通过索引值找到队列;flag打开队列的方式
4 // 发送消息:成功返回0,失败返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
msgid:由msgget函数返回的消息队列标识码
*ptr:准备发送的消息
size:发送的消息的长度(不包括消息类型的long int长整型)
flag:默认为0
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);//
发送消息:
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
struct msg
{
long type;
char text[128];
};
int main()
{
struct msg sendbuf = {888,"this is from que"};
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1)
{
printf("creat que fail\n");
}
msgsnd(msgID,&sendbuf,sizeof(sendbuf.text),0);
return 0;
}
读取消息:
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
struct msg
{
long type;
char text[128];
};
int main()
{
struct msg readbuf;
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1)
{
printf("creat que fail\n");
}
msgrcv(msgID,&readbuf,sizeof(readbuf.text),888,0);// 返回类型为888的第一
个消息
printf("Server: receive msg.mtext is: %s.\n",readbuf.text);
return 0;
}
发送消息的时候要给队列的结构体定义类型888,读取消息的时候要给读取队列的参数写上这个类型。
原理就是获取队列,写队列,获取队列,读队列。
下面我们来尝试下消息队列既收消息又发消息,
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
struct msg//消息队列里面本来就有这样一个结构体,这里只是模仿了出来
{
long type;
char text[128];
};
int main()
{
struct msg readbuf;
struct msg sendbuf = {888,"this is from que"};
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1)
{
printf("creat que fail\n");
}
msgsnd(msgID,&sendbuf,sizeof(sendbuf.text),0);//把队列号888内容发送出去
printf("send over\n");
msgrcv(msgID,&readbuf,sizeof(readbuf.text),988,0);//把队列号988内容读出来
printf("Server: receive msg.mtext is: %s.\n",readbuf.text);
printf("read over\n");
msgctl(msgID,IPC_RMID,NULL);//移除队列
return 0;
}
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
struct msg
{
long type;
char text[128];
};
int main()
{
struct msg readbuf;
struct msg sendbuf = {988,"this is from read"};
int msgID = msgget(0x1234,IPC_CREAT|0777);
if(msgID == -1)
{
printf("creat que fail\n");
}
msgrcv(msgID,&readbuf,sizeof(readbuf.text),888,0);// 从队列号888里面读消息,读到readbuf里面去
printf("Server: receive msg.mtext is: %s.\n",readbuf.text);//打印读到的内容
printf("read over\n");
msgsnd(msgID,&sendbuf,sizeof(sendbuf.text),0);//把sendbuf的内容发到队列号是988的队列中去
printf("send over\n");
msgctl(msgID,IPC_RMID,NULL);//移除队列
return 0;
}
key_t键和ftok函数:
key_t key;
key = ftok(".",'z');//获取当前队列号
printf("key is %x\n",key);
msgget(key,IPC_CREAT|0777)
pathname:指定的文件,此文件必须存在且可存取
proj_id:计划代号(project ID)
成功:返回key_t值(即IPC 键值)
出错:-1,错误原因存于error中
4、共享内存
#include <sys/shm.h>
1 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
(1)生成键值
(2)开辟的大小必须以M为基本单位
(3)共享内存权限
2 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
(1)共享内存的ID
(2)一般写0,让系统自动分配共享内存的地址
(3)一般写0,内存可读可写
3 // 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
(1)共享内存连接成功后返回的指针
4 // 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
(1)共享内存ID
(2)IPC_RMID,一般写这个
(3)不关心这个写0
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int shmID;
char* shmadr;
key_t key;
key = ftok(".",1);
shmID = shmget(key,1024*4,IPC_CREAT|0600);//没有就创建为可读可写
if(shmID == -1)
{
printf("cread shm fail\n");
exit(-1);
}
shmadr = shmat(shmID,0,0);//理解为创建共享内存地址
printf("shmat ok\n");
strcpy(shmadr,"welcome to linux");//往共享内存写入这么一段话
sleep(5);
shmdt(shmadr);//断开与共享内存的连接
shmctl(shmID,IPC_RMID,0);//释放共享内存
printf("quit\n");
return 0;
}
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int shmID;
char* shmadr;
key_t key;
key = ftok(".",1);
shmID = shmget(key,1024*4,0);
if(shmID == -1)
{
printf("cread shm fail\n");
exit(-1);
}
shmadr = shmat(shmID,0,0);
if(shmadr != NULL)
{
printf("shmat ok\n");
}
printf("data = %s\n",shmadr);//读取共享内存数据
sleep(5);//延时5秒为使从共享内存读数据有缓冲时间
shmdt(shmadr);//断开与共享内存的连接
printf("quit\n");
return 0;
}
从共享内存读数据时,必须要在这延时的5秒内进行,否则共享内存将被释放,无法与共享内存建立联系。
ipcs -m 查看共享内存的ID值和大小
ipcrm -m shmid 移除未释放的共享内存
5、信号
假如有两个进程,进程二给进程一一个信号,那么进程1对相应的信号做出一个应对,同样信号也有优先级,看优先处理哪个信号,类似中断。可用kill -l 查看信号的名字及序号。kill -9 信号ID杀死信号。
信号的处理有三种办法,忽略,捕捉,默认。
核心:捕捉到一个信号而去执行其他信号,这里的其他信号需要自己构造,handler
(1)捕捉信号
捕捉信号:
sighandler_t signal(int signum, sighandler_t handler)
第一个参数signum:表示要捕捉哪个信号。
第二个参数handler:函数指针,当捕捉到这个信号时,执行信号处理函数
这是调用信号:
#include <signal.h>
#include <stdio.h>
void handler(int signum)//捕捉到信号时自动把信号代数送进去
{
printf("get signum is %d\n",signum);
printf("never quit\n");
}
int main()
{
signal(SIGINT,handler);//捕捉到SIGNAL指令时,自动执行handler指令
signal(SIGKILL,handler);
while(1);
return 0;
}
SIGINT:程序终止信号,即按ctrl+c
SIGKILL:终止信号
ps -aux|grep test:列出进程号 R+代表正在运行的进程
kill -9 进程号 杀死进程
发送信号:
#include <signal.h>
#include <stdio.h>
int main(int argc,char* argv[3])//命令行的都是字符串形式
{
int signum;
int pid;
char cmd[128];
signum = atoi(argv[1]);//字符串转换为整型数的函数
pid = atoi(argv[2]);
printf("num = %d,pid = %d\n",signum,pid);
sprintf(cmd,"kill -%d %d",signum,pid);//做出一个cmd指令,是kill xx xx型的,>参数是后面的
//kill(pid,signum);//类似kill -9 pid
system(cmd);//让系统运行杀死指令
printf("send ok\n");
return 0;
}
发送信号用kill发,绑定操作函数用signal来绑定。
各种不同的信号量的代数。
(2)忽略信号
ps -aux|grep -xinhao:查看后缀为xinhao的信号,用kill -9 信号号来杀死信号
#include <signal.h>
#include <stdio.h>
void handler(int signum)//捕捉到信号时自动把信号代数送进去
{
printf("get signum is %d\n",signum);
printf("never quit\n");
}
int main()
{
signal(SIGINT,SIG_IGN);//第一个参数是要忽略的信号,第二个参数是忽略宏定义
signal(SIGINT,handler);//捕捉到SIGNAL指令时,自动执行handler指令
signal(SIGKILL,handler);
while(1);
return 0;
}
(3)高级信号编程
用sigaction来收信号,用sigqueqe来发送信号。
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
(1)第一个参数表示接受的信号。
(2)第二个参数表示收到这个信号想干嘛,需要构造结构体
(3)第三个参数表示备份,通常NULL
//接收信号
开发流程:配置act结构体、配置处理函数
#include <signal.h>
#include <stdio.h>
void handler (int signum, siginfo_t *info, void *context)
{
printf("get signum = %d\n",signum);
if(context != NULL)
{
printf("get data = %d\n",info->si_int);//这里是指针所以要用->,收到信号内容
printf("get data = %d\n",info->si_value.sival_int);//同样是信号内容
printf("from %d\n",info->si_pid);//发送者的pid
}
}
int main()
{
struct sigaction act;
printf("pid = %d\n",getpid());
act.sa_flags = SA_SIGINFO;//要收信号就把这个参数设置为SA_SIGINFO
act.sa_sigaction = handler;//收到信号处理该函数
sigaction(SIGUSR1,&act,NULL);//接收SIGUSR1信号,SIGUSR1代号是10
while(1);
return 0;
}
接收信号步骤:1、sigaction()收到SIGUSR1信号执行act,第三个参数用来备份,
2、要接收信号,必须把act构造为结构体,并且设置里面参数为
act.sa_flags = SA_SIGINFO;//要收信号就把这个参数设置为SA_SIGINFO
act.sa_sigaction = handler;//收到信号处理该函数
3、handler收到该信号先把该信号值打出来
4、判断context内容,如果有内容就把info里面的数据打出来
//发送信号
#include <signal.h>
#include <stdio.h>
// int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);//信号代号,SIGUSR1代号是10
pid = atoi(argv[2]);//要发送到的进程号
union sigval value;
value.sival_int = 100;//写入要发送的数据
sigqueue(pid,signum,value);//给这个进程发送数据
printf("%d done\n",getpid());
return 0;
}
5、信号量
设想一个场景:有一个房间,有一把钥匙,当A拿走要是进入该房间时,B不能进入到该房间,
只有当A把钥匙放回,B才能进入该房间。
钥匙就叫信号量,房间就称为临界资源,共享内存就是一种临届资源,当处理A进程时,B进程不能被处理。
p操作:拿锁 V操作:放回锁
信号量操作步骤:
1、获取信号量
2、信号量初始化
一、int semget(key_t key, int num_sems, int sem_flags);//获取信号量
key:根据以往经验,获取键值 key_t key key = ftok(".",2).
num_sems:信号量集中信号量个数。这里我们设为1
sem_flags:创建信号量IPC_CREAT|0666
二、int semctl(int semid, int sem_num, int cmd, ...);//初始化信号量
semid:信号量ID
sem_num:对第几个信号量操作,从0开始
cmd:对信号量操作什么,SETVAL就是设置信号量初值
第四个参数:构造信号量结构体
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) */
};
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
union semun
{
int val; /* 信号量个数 */
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) */
};
int main()
{
key_t key;
int semid;
union semun initsem;
key = key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);//信号量创建成功
initsem.val = 1;
semctl(semid,0,SETVAL,initsem);//初始化信号量
return 0;
}
P操作主要是检测信号量S是否大于0,假如大于0,那么久使得信号量减1,假如小于等于0,那么就忙式等待,做空循环;
V操作主检测信号量,给信号量做加1操作;
依据这个操作,实现一个创建进程,默认锁数量为0,父进程拿不到锁,只有当子进程运行
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
union semun
{
int val; /* 信号量个数 */
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) */
};
void pGetKey(int id)//拿走锁,最终目的:锁数量-1
{
struct sembuf set;
set.sem_num=0;//对第0个锁进行操作
set.sem_op=-1;//拿走锁,锁数量-1
set.sem_flg=SEM_UNDO;//一般都用这个
semop(id,&set,1);//
printf("getKey\n");
}
void vPutKey(int id)//放回锁,最终目的:锁数量+1
{
struct sembuf set;
set.sem_num=0;//对第0个锁进行操作
set.sem_op=1;//放回锁,锁数量+1
set.sem_flg=SEM_UNDO;//一般都用这个
semop(id,&set,1);//
printf("putKey\n");
}
int main()
{
key_t key;
int semid;
int pid;
union semun initsem;
key = key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);//信号量创建成功
initsem.val = 0;//初始化锁数量
semctl(semid,0,SETVAL,initsem);//初始化信号量
pid = fork();
if(pid>0)
{
pGetKey(semid);//拿到锁,代码才会运行
printf("this is father\n");
vPutKey(semid);
}
if(pid == 0)
{
printf("this is child\n");
vPutKey(semid);
}
return 0;
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
union semun
{
int val; /* 信号量个数 */
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) */
};
void pGetKey(int id)//拿走锁,最终目的:锁数量-1
{
struct sembuf set;
set.sem_num=0;//对第0个锁进行操作
set.sem_op=-1;//拿走锁,锁数量-1
set.sem_flg=SEM_UNDO;//一般都用这个
semop(id,&set,1);//
printf("getKey\n");
}
void vPutKey(int id)//放回锁,最终目的:锁数量+1
{
struct sembuf set;
set.sem_num=0;//对第0个锁进行操作
set.sem_op=1;//放回锁,锁数量+1
set.sem_flg=SEM_UNDO;//一般都用这个
semop(id,&set,1);//对信号量进行操作,改变信号量的值
printf("putKey\n");
}
int main()
{
key_t key;
int semid;
int pid;
union semun initsem;
key = ftok(".",2);
semid = semget(key,1,IPC_CREAT|0666);//信号量创建成功,返回信号量ID
initsem.val = 0;//初始化锁数量
semctl(semid,0,SETVAL,initsem);//初始化信号量
pid = fork();
if(pid>0)
{
pGetKey(semid);
printf("this is father\n");
vPutKey(semid);
}
if(pid == 0)
{
printf("this is child\n");
vPutKey(semid);
}
return 0;
}
首先对创建信号量,并且获取信号量ID,其次初始化信号量(锁为0),创建P,V操作,创建进程,父进程先拿锁(锁数量为0,拿不到),只有等子进程放锁,父进程才能拿到,巧妙地设计了一个让子进程先运行的案例。