进程间通信

什么是进程间通信?
两个进程可以同时往公共内存区(管道)存或发数据这样的全双工通信是真正意义上的通信。
消息队列包括:无名管道(父子进程通信),命名管道(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;
}

接收信号步骤:1sigaction()收到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,拿不到),只有等子进程放锁,父进程才能拿到,巧妙地设计了一个让子进程先运行的案例。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

￴ㅤ￴￴ㅤ9527超级帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值