用exit和exec无法完全进程间通信
进程间通信IPC有:无名管道pipe、有名管道、消息队列、信号量、共享存储、socket。strams流 两个支持不同主机上两个进程ipc
无名管道pipe(int fd[2]),成功返回0,失败返回-1,fd01 读写
:特点 半双工 、用于亲缘间通信、内在内存中 进程结束即消失
//pipe适用于亲缘之间通信 成功返回0 失败返回-1、fd[0]读 fd[1]写
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
// ssize_t write(int fd, const void *buf, size_t count);
int main()
{
int fd[2];
pid_t pid;
char *p = "hello world!!";
char buf[128] = {0};
if(pipe(fd) == -1){ //创建无名管道
printf("creat pipe fail\n");
}
pid = fork(); //创建子进程
if(pid == -1){ //创建失败
printf("creat fork fail\n");
}
else if(pid > 0){ //父进程 关闭读 开启写
sleep(3);
printf("this is the father\n");
close(fd[0]);
write(fd[1],p,strlen(p));
wait(NULL);
}
else{ //子进程 关闭写 开启读 如果没有读到就会阻塞在read
printf("this is the child\n");
close(fd[1]);
read(fd[0],buf,128);
printf("zi jin cheng read:%s\n",buf);
}
return 0;
}
FIFO 命名管道 是一种文件类型 int mkfifo(*path,mode)成功返回0 失败返回-1,mode和open函数一样,可以与无关进程相互交换数据,
如果要实现命名管道间相互交换数据、必须一读一写 搭配使用才行。
读写配合
//命名管道通信:mkfifo先创建管道(读或者写端创建管道)对该文件 一端读 一端写
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
int main() //创建管道 并阻塞读取内容
{
// int mkfifo(const char *pathname, mode_t mode);
int mk;
mk = mkfifo("./file1",0600); //创建管道 正确返回0 错误返回-1
if((mk == -1) && errno != EEXIST){ //创建失败 且错误号不为EEXIST
printf("creat mkfifo fail\n"); //errno 错误号
perror("why:");
}
else if(mk == 0){ //创建管道成功
printf("creat mkfifo succeed\n");
}
int fd;
char buf[30] = {0};
fd = open("./file1",O_RDONLY); //在管道中打开该文件
while(1){
int n_read = read(fd,buf,30); //阻塞读取内容
printf("read:%d %s\n",n_read,buf);
sleep(1);
}
close(fd);
return 0;
}
int main()
{
int cnt = 0;
int fd;
char *str = "mkfifo succeed!!!";
fd = open("./file1",O_WRONLY); //打开管道中的文件 并写内容
while(1){
int n_write = write(fd,str,strlen(str));//写内容
cnt++;
if(cnt == 5){
break;
}
printf("write:%d %s\n",n_write,str);
sleep(2);
}
close(fd);
return 0;
}
消息队列、
key_t key;
key = ftok(".",'A');
int msg_id = msgget(key,IPC_CREAT|0777);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//消息队列 创建消息队列 存放或读取消息队列结构体里面的消息 发送和接收端口号要一致
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf { //获取到消息队列id后 信息再这个结构体里面
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
//intmsgflg);
key_t key;
key = ftok(".",5); //fork函数创建一个键值,"."当前路径 5/A键值
printf("key:%x\n",key);
struct msgbuf readBuf;
struct msgbuf writeBuf = {988,"zhe shi 988 jie shou\n"};
int msgId = msgget(key,IPC_CREAT|0777); //获取消息队列的ID
if(msgId == -1){ //获取失败
printf("msgget faile!\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
//依据消息队列ID读取结构体里面的消息 根据端口号对接 没有的话会阻塞在这里等待
printf("888 readBuf:%s\n",readBuf.mtext); //打印获取到的消息
msgsnd(msgId,&writeBuf,strlen(writeBuf.mtext),0); //发送端口号998 结构体里面的内容
printf("get:send over!!\v");
msgctl(msgId,IPC_RMID,NULL); //防止产生过多队列 删除该消息队列
return 0;
}
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[128]; /* message data */
};
int main()
{
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
key_t key;
key = ftok(".",5);
printf("key:%x\n",key);
struct msgbuf writeBuf = {888,"zhe shi 888 jie shou\n"};
struct msgbuf readBuf;
int msgId = msgget(key,IPC_CREAT|0777);
if(msgId == -1){
printf("msgget faile!\n");
}
msgsnd(msgId,&writeBuf,strlen(writeBuf.mtext),0);
printf("send:send over!\n");
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
printf("988 readBuf:%s\n",readBuf.mtext);
msgctl(msgId,IPC_RMID,NULL);
return 0;
}
共享内存
//写 创建共享内存 指针映射内存 写数据 删地址 删共享内存
//读 获取同一个共享内存 映射 读数据--打印 删地址
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <string.h>
int main()
{
// int shmget(key_t key, size_t size, int shmflg);
char * shmaddr = NULL;
key_t key; //键值
key = ftok(".",1);
int shmId = shmget(key,2,IPC_CREAT|0777); //创建共享内存以M对齐成功返回id 失败返回-1
if(shmId == -1){
printf("shamget faile!\n");
perror("why:\n");
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmId,0,0); //映射到一块地址空间 00 默认配置
strcpy(shmaddr,"hello world!"); //写入数据
printf("xie ru succeed!\n");
sleep(5); //延时等待读取
// int shmdt(const void *shmaddr);
shmdt(shmaddr); //断开连接
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgctl(shmId,IPC_RMID,0); //删除共享内存
return 0;
}
int main()
{
// int shmget(key_t key, size_t size, int shmflg);
char * shmaddr = NULL;
key_t key;
key = ftok(".",1);
int shmId = shmget(key,2,IPC_CREAT|0777); //获取同意共享内存 返回id 失败返回-1
if(shmId == -1){
printf("shamget faile!\n");
perror("why:\n");
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr = shmat(shmId,0,0); //共享内存映射到地址空间
printf("read:%s\n",shmaddr); //读取内容 打印出来
// int shmdt(const void *shmaddr);
shmdt(shmaddr); //打开连接
return 0;
}
int main()
{
int shmget_id;
char * addr;
key_t key;
key = ftok(".",2);
shmget_id = shmget(key,1024*4,IPC_CREAT|0644);
if(shmget_id == -1)
{
printf("shmget failuer\n");
perror("why");
}
addr = shmat(shmget_id,0,0);
printf("shmat ok!!\n");
strcpy(addr,"hello world");
sleep(5);
shmdt(addr);
shmctl(shmget_id,IPC_RMID,0);
printf("quit!!\n");
return 0;
接收部分
int shmget_id;
char * addr;
key_t key;
key = ftok(".",2);
shmget_id = shmget(key,1024*4,0);
if(shmget_id == -1)
{
printf("shmget failuer\n");
exit(-1);
}
addr = shmat(shmget_id,0,0);
printf("recrive over\n");
printf("datas:%s\n",addr);
// shmdt(shmaddr);
// shmctl(shmget_id,IPC_RMID,0);
// printf("quit!!\n");
return 0;
信号 signal(signum信号编号 传给handler, handler是中断函数指针);----》void handler(signum) 对应demo2 signalDemo1/2/3.c
//int kill(pid_t pid, int sig);
//typedef void (*sighandler_t)(int);
//sighandler_t signal(int signum, sighandler_t handler);
x信号有三种处理方式 :忽略(SIGKILL和SIGSTOP不能忽略)、捕获(写一个中断函数)、默认
kill -l 查看signum ps aux|grep ./a.out查看pid
//进程1 用signal 注册信号处理函数,handler信号处理-- 编译运行aa ps查看该信号pid
//进程2 通过main函数传参 把进程1的信息 signum和pid传进来 用kill函数 或system来实现通信
#include <stdio.h>
#include <signal.h>
#include <stdio.h>
void handler(signum)//信号处理函数
{
switch(signum){
case 2:printf("signum:%d\n",signum);break;//捕获SIGINT信号
/*用户键入 Ctrl + C 产生。当接收进程接收到这个信号时,signal_handler函数会被调用,并打印出接收到的信号编号。其他指令只能通过ps -aux|grep ./a.out 查看pid kill -x pid*/
case 3:printf("signum:%d\n",signum);break;
case 9:printf("signum:%d\n",signum);break;//杀死信号 不能捕获
}
}
int main()
{
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
signal(SIGINT,handler); //捕获 注册信号处理函数
signal(SIGQUIT,handler);
signal(SIGKILL,handler);
while(1); //等待信号
return 0;
}
//进程2
int main(int argc,char **argv) //传入进程1 的signum和pid
{
int signum;
int pid;
char cmd[128];
signum = atoi(argv[1]); //把字符串转换成整型数
pid = atoi(argv[2]); //把字符串转换成整型数
printf("signum:%d pid:%d\n",signum,pid);
/*
int k_id = kill(pid,signum); //方法一:kill执行kill -signum pid
if(k_id == -1){ //错误返回-1
printf("kill falie!\n");
}
*/
sprintf(cmd,"kill -%d %d",signum,pid); //方法二:把中间字符串放到cmd里面
system(cmd); //调用函数 执行这个指令
printf("cmd:%s\n",cmd);
return 0;
}
signal()函数
作用:注册一个信号捕捉函数
typedef void (*sighandler_t)(int);
函数原型:sighandler_t signal(int signum, sighandler_t handler);
函数参数:signum:欲要设置捕捉的信号编号;
handler:回调函数,信号捕捉后所要执行的函数;
注意:该函数由 ANSI 定义,由于历史原因在不同版本的 Unix 和不同版本的 Linux 中可能有不同的行为。因此应该尽量避免使用它,取而代之使用 sigaction 函数。
sigaction() 函数
作用:修改(捕捉)信号处理动作(通常在 Linux 用其来注册一个信号的捕捉函数)
函数原型: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
返回值:成功:0;失败:-1,设置 errno
参数:signum:欲要设置捕捉的信号编号;
act:传入参数,新的处理方式。
oldact:传出参数,旧的处理方式。
struct sigaction 结构体
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
sa_restorer:该元素是过时的,不应该使用,POSIX.1 标准将不指定该元素。(弃用)
sa_sigaction:当 sa_flags 被指定为 SA_SIGINFO 标志时,使用该信号处理程序。(很少使用)
重点掌握:
① sa_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为 SIG_IGN 表忽略 或 SIG_DFL 表执行默认动作
② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
③ sa_flags:通常设置为 0,表使用默认属性。
信号捕捉特性:
进程正常运行时,默认 PCB 中有一个信号屏蔽字,它决定了进程自动屏蔽哪些信号。当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由默认mask来指定。而是用 sa_mask 来指定。调用完信号处理函数,再恢复为默认mask。
XX 信号捕捉函数执行期间,XX 信号自动被屏蔽。
阻塞的常规信号不支持排队,产生多次只记录一次。(后 32 个实时信号支持排队)
信号高级 发送接收
//高级信号接收sigaction
#include <stdio.h>
#include <signal.h>
/*
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
*/
void handler(int signum,siginfo_t *info,void *context) //3 回调函数handler三个参数
{
printf("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);//指针里面结构体的值
}
}
int main()
{
struct sigaction act; //2 信号处理函数 结构体 封装成act
act.sa_sigaction = handler; //配置结构体的sa_sigaction 回调handler函数
act.sa_flags = SA_SIGINFO; //sa_flags为SA_SIGINFO才会执行handler函数
// int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
int sigId = sigaction(SIGUSR1,&act,0); //1 获取信号函数 信号 处理函数 不备份
if(sigId == -1){
printf("sigaction failer!\n");
}
printf("%d\n",getpid());
while(1);
return 0;
}
//发送sigqueue
/*
union sigval {
int sival_int;
void *sival_ptr;
};
*/
int main(int argc,char **argv) //通过这个主函数入口把接收函数的signum 和pid值传进来
{
int signum;
int pid;
signum = atoi(argv[1]); //转换成整型
pid = atoi(argv[2]);
union sigval value;//这个是联合体 即把联合体里面的值传给接收函数
value.sival_int = 100;
printf("my pid:%d\n",getpid());
// int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue(pid,signum,value); //封装第三个参数
printf("send over!\n");
return 0;
}
信号量!!:用于进程间互斥和同步、不存储信息 3个API
semget获取信号量 semctl配置信号量 semop拿锁放回锁
key相当于信息量、多个信息量---信号量集、房间或者进程叫临界资源 一次只允许一个进程使用所以用到key,拿到钥匙给他上锁 别人进不去、出来再放回锁
//信号量 获取创建信号量semget、初始化semctl、fork父子进程、拿锁放锁semop
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// int semget(key_t key, int nsems, int semflg);
// int semctl(int semid, int semnum, int cmd, ...);
// int semop(int semid, struct sembuf *sops, unsigned nsops);
// int semctl(int semid, int semnum, int cmd, ...);
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) */
};
void pGetkey(int semid){ //拿锁
struct sembuf sops; //定义semop里面的结构体、也可以是结构体数组 sops[2] nsops = 2
sops.sem_num = 0; //起始信号量为0
sops.sem_op = -1; //拿锁 锁减1
sops.sem_flg = SEM_UNDO; //里面没锁 阻塞
semop(semid,&sops,1); //拿锁函数 id sops结构体配置 sops个数
}
void vBanckkey(int semid){ //放锁
struct sembuf sops;
sops.sem_num = 0;
sops.sem_op = +1; //放锁 锁+1
sops.sem_flg = SEM_UNDO;
semop(semid,&sops,1); //
}
int main()
{
key_t key = ftok(".",18);
int semId = semget(key,1,IPC_CREAT|0600); // 1 创建信号量 返回信号量id
if(semId == -1){
printf("semget error!\n");
}
union semun value;
value.val = 0; //锁的个数
semctl(semId,0,SETVAL,value); // 2 信号量初始化参数:id 起始信号量编号0
//SETVAL是信号量的值 具体值通过value联合体设置-----即锁的个数
pid_t pid = fork();
if(pid > 0){
pGetkey(semId);//父进程 拿不到锁等待
printf("this is the father!\n");
vBanckkey(semId); //放回锁
}
else if(pid == 0){ //子进程有锁 放回去 子进程先运行
printf("this is the child!\n");
printf("father wait 3s!\n");
sleep(3);
vBanckkey(semId);
}
else{
printf("fork error!\n");
}
semctl(semId,1,SETVAL,value); //销毁锁
return 0;
}
//首先用semget获取信号量、对信号量初始化、fork调用子进程、针对父进程和子进程P操作V操作
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdio.h>
//int semop(int semid, struct sembuf *sops, unsigned nsops);
//锁函数 (信号量id,结构体--取地址 不是指针,nsops几个结构体数组1)
void vBackKey(int id)//放回去锁
{ struct sembuf set;
set.sem_num = 0;//信号量编号 0开始
set.sem_op = 1;//放锁+1
set.sem_flg= SEM_UNDO;//相当于等待
semop(id,&set,1);//放钥匙
printf("key: fang hui qu\n");
}
void pGetKey(int id)//拿锁
{ struct sembuf set;
set.sem_num = 0;//信号量编号
set.sem_op = -1;//拿锁-1
set.sem_flg= SEM_UNDO;//等待
semop(id,&set,1);//拿锁
printf("key: na dao key\n");
}
union semun {//与initsem有关 一般只取第一个
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) */
};
// int semget(key_t key, int nsems, int semflg);
int main(int argc,char const *argv[])
{
key_t key;
key = ftok(".",1);
int semid = semget(key,1,IPC_CREAT|0666);//(key,几个信号量1,权限)
// int semctl(int semid, int semnum, int cmd, ...);//(semid,几个一般从0开始算,cmd决定三个还是4个参数 如果是SETVAL 四个参数第四个由结构体构成,RMID则只要三个参数);
union semun initsem;
initsem.val = 0;//设置刚开始里面没锁 则父进程没法拿锁 由子进程放锁后才有锁 再执行父进程
semctl(semid,0,SETVAL,initsem);
int pid = fork();
if(pid > 0){
pGetKey(semid);//拿锁----没锁会一直等待
printf("this is the father\n");
vBackKey(semid);//放回锁
}
else if(pid == 0){
vBackKey(semid);//放回锁
printf("this is the child\n");
}
else{
printf("pid error\n");
}
return 0;
}