华清远见25072班I/O学习day4
重点内容:
三、进程间通信(IPC)
3.1 进程间通信的引入
1> 多个进程之间能否使用全局变量来进行进程间通信?
答:不能,多个进程之间的用户空间是相互独立的,每个进程的全局变量是各个进程独立拥有的,更改一个进程的全局变量,另一个进程不会受到影响,故而不能使用全局变量来完成通信
2> 有同学说:使用文件来完成进程间通信?
答:可行,但是要求写进程先完成向文件中写入数据,然后读进程才能读取数据,必须要加入进程的同步操作
3> 多个进程之间内核空间是共享的,我们可以通过将数据放入到内核空间,来完成两个进程之间信息的交流
4> 进程间通信方式:
1、内核提供的原始通信方式
无名管道
有名管道
信号
2、system V提供三种
消息队列
共享内存
信号量集
3、套接字通信(网络编程)
3.2 信号通信
1> 实现一个进程向另一个进程发送信号,能够发送的信号是固定,有62个,可以通过 man kill 查看
2> 通过函数 kill 将一个信号从一个进程中发送给另一个进程,但是,不能发送数据(只起到通知作用)
3> 注意:需要信号的发射者进程,直到要被发送信号进程的pid,一般发生在父子进程之间
4> 对于信号的接受进程而言,需要使用signal函数,将指定信号,绑定到指定操作上,后期一旦该进程收到相关信号,直接去执行相关函数
5> 相关信号:一般情况下,一个进程收到一个信号,默认的处理方式是杀死进程
6> 信号绑定函数(signal)
sighandler_t signal(int signum, sighandler_t handler);
功能:将信号号跟信号处理方式绑定到一起
参数1:要处理的信号号
参数2:处理方式,一共有三种处理方式
SIG_IGN:表示忽略该信号
SIG_DFL:执行该信号对应的默认处理方式
用户自定义功能(捕获):参数为int类型,返回值为void类型的函数名
返回值:成功返回处理方式的起始地址,失败返回SIG_ERR并置位错误码
注意:SIGKILL和SIGSTOP信号既不能被捕获,也不能被忽略
7> 向其他进程发送信号的函数(kill)
int kill(pid_t pid, int sig);
功能:将指定的信号发送给指定的进程
参数1:要接受信号的进程pid
参数2:要被发送的信号
>0: 该信号发送给指定的进程中
=0:表示该信号会发送到当前进程所在的进程组中的任意一个进程中
=-1:表示将该信号发送给除当前进程外的所有进程
<-1:将该信号发送到一个进程组中的任意一个进程,给定的pid的绝对值进程所在的进程组
返回值:成功返回0,失败返回-1并置位错误码
8> 以信号的方式回收僵尸进程
1、 当子进程退出时,会自动向其父进程发送一个SIGCHLD信号,告诉父进程自己已经死了
2、 但是父进程收到该信号后,默认的处理方式是忽略
3.3 消息队列
前提指令:有关IPC对象的指令
ipcs:可以查看消息队列、共享内存、信号量集的所有信息
ipcs -q: 只查看消息队列的所有信息
ipcs -m:只查看共享内存的所有信息
ipcs -s:只查看信号量集的所有信息
ipcs -r ID:表示要删除某个ICP对象
System V提供的进程间通信对象,是对立与进程而存在的
1>有关消息队列的函数 API
key_t ftok(const char *pathname, int proj_id);
功能:通过给定的文件或目录路径(必须存在)以及一个随机数据来生成一个key值 ftok("\", 'k')
参数1:已经存在的文件路径,一般我们使用 "\"
参数2:一个任意给定的随机数,只需要低8位即可
返回值:成功返回ipc对象的key值,失败返回(key_t)-1并置位错误码
int msgget(key_t key, int msgflg);
功能:通过给定的key值创建出一个消息队列,并返回该消息队列的id,以后使用该id就可以操作该消息队列了
参数1:key值,是用于创建消息队列的key值,该值可以是IPC_PRIVATE(仅仅用于亲缘进程间通信使用),非亲缘进程间通信需要使用 ftok 创建一个key值
参数2:消息队列的创建模式: IPC_CREAT|IPC_EXCL|0664
IPC_CREAT:打开一个消息队列,如果消息队列不存在,则创建一个消息队列 IPC_EXCL:确保本次必须创建出一个新的消息队列,如果消息队列已经存在,则报错,错误码为EEXISTS
还需要加上创建权限
返回值:成功返回打开的消息队列的id号 以后使用该id就可以操作该消息队列了
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向msqid记录的消息队列中放入以msgp指针指向的消息,其消息正文大小为msgsz
参数1:消息队列id号
参数2:消息的起始地址,需要用户自定义一个结构体
结构体的通用格式为 struct msgbuf { long mtype; /* 消息类型 */ char mtext[1024]; /* 消息正文 */ };
参数3:消息正文的大小
参数4:是否阻塞发送
0:表示阻塞形式发送
IPC_NOWAIT:表示非阻塞形式发送
返回值: 成功返回0,失败返回-1并置位错误码
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从msqid消息队列中取出消息,并放到msgp指向的容器中,取出的正文大小为msgsz,取出消息的类型为msgtyp
参数1:消息队列id号
参数2:消息的起始地址,需要用户自定义一个结构体
结构体的通用格式为 struct msgbuf { long mtype; /* 消息类型 */ char mtext[1]; /* 消息正文 */ };
参数3:消息正文的大小
参数4:取出消息的类型
=0:表示无关类型,直接取第一个放入消息队列中的消息
>0:表示取出给定类型中的第一个存放到消息队列中的消息
<0: 表示取得1--该数的绝对值类型中的第一个存放到消息队中的消息
例如 -5 表示从类型为1---5之间存放进入的第一个消息
参数5:是否阻塞发送
0:表示阻塞形式发送
IPC_NOWAIT:表示非阻塞形式发送
作业:
1>使用消息队列实现两个程序的相互通信
原理图:
程序源码:(s.out)
#include <25072head.h>
//自定义消息类型的结构体
struct msgbuf
{
long mtype; //消息类型
char mtext[1024]; //消息正文
};//宏定义一个消息正文大小
#define SIZE (sizeof(struct msgbuf)-sizeof(long))
int main(int argc, const char *argv[])
{
pid_t pid=-1;
pid=fork();
if(pid>0)
{
//1.创建一个key值
key_t key=ftok("/",'k');
if(key==-1)
{
perror("ftok error\n");
return -1;
}
//2.使用创建出来的key值创建一个消息队列
int msqid=msgget(key,IPC_CREAT|0664);
if(msqid==-1)
{
perror("msgget error");
return -1;
}
//3.不断在消息队列中放入消息
struct msgbuf buf;
while(1)
{
printf("请输入消息类型:");
scanf("%ld",&buf.mtype);
getchar();
printf("请输入消息正文:");
fgets(buf.mtext,SIZE,stdin);
buf.mtext[strlen(buf.mtext)-1]=0;
//表示退出不再往里面放数据
if(strcmp(buf.mtext,"quit")==0)
{
break;
}
//将消息放入到消息队列中
msgsnd(msqid,&buf,SIZE,0);
}
}
else if(pid==0)
{
//1.创建一个key值
key_t key1=ftok("/",'k');
if(key1==-1)
{
perror("ftok error\n");
return -1;
}int msqid1=msgget(key1,IPC_CREAT|0664);
if(msqid1==-1)
{
perror("msgget error");
return -1;
}
struct msgbuf buf1;
while(1)
{
msgrcv(msqid1,&buf1,SIZE,2,0);
printf("取出的消息为:%s\n",buf1.mtext);
}
}
return 0;
}
程序源码:(r.out)
#include<25072head.h>
//自定义消息类型的结构体
struct msgbuf
{
long mtype; //消息类型
char mtext[1024]; //消息正文
};//宏定义一个消息正文大小
#define SIZE (sizeof(struct msgbuf)-sizeof(long))
/******************8主程序********************/
int main(int argc, const char *argv[])
{
pid_t pid=-1;
pid=fork();
if(pid>0)
{
//1.创建一个key值
key_t key = ftok("/", 'k');
if(key == -1)
{
perror("ftok error\n");
return -1;
}//2.使用创建出来的key值创建一个消息队列
int msqid = msgget(key, IPC_CREAT|0664);
if(msqid == -1)
{
perror("msgget error");
return -1;
}//3.不断从消息队列中取出消息
struct msgbuf buf;
while(1)
{
//从消息队列中取出消息
msgrcv(msqid, &buf, SIZE, 1, 0);
printf("取出的消息为:%s\n", buf.mtext);
}
}
else if(pid==0)
{
//1.创建一个key值
key_t key1 = ftok("/", 'k');
if(key1 == -1)
{
perror("ftok error\n");
return -1;
}int msqid1 = msgget(key1, IPC_CREAT|0664);
if(msqid1 == -1)
{
perror("msgget error");
return -1;
}
struct msgbuf buf1;
while(1)
{
printf("请输入消息类型:");
scanf("%ld", &buf1.mtype);
getchar();
printf("请输入消息正文:");
fgets(buf1.mtext, SIZE, stdin); //从终端读取
buf1.mtext[strlen(buf1.mtext)-1] = 0;//表示退出不再往里面放数据
if(strcmp(buf1.mtext,"quit")==0)
{
break;
}
//将消息放入到消息队列中
msgsnd(msqid1, &buf1, SIZE, 0);
}
}
return 0;
}
2> 使用信号实现非阻塞方式回收僵尸进程练习
程序源码:
#include<25072head.h>
//自定义信号处理函数
void handler(int signum)
{
//判断信号
if(signum == SIGCHLD)
{
//直到一定没有僵尸进程时,结束循环
while(waitpid(-1, NULL, WNOHANG)>0);
}
}
int main(int argc, const char *argv[])
{
//将子进程退出时的信号SIGCHLD绑定到自定义函数中
if(signal(SIGCHLD, handler) == SIG_ERR)
{
perror("signal error");
return -1;
}
for(int i=0; i<10; i++)
{
if(fork() == 0)
{
exit(EXIT_SUCCESS);
}
}
while(1)
{
if(fork() == 0)
{
exit(EXIT_SUCCESS);
}
}
return 0;
}
3> 思维导图
4> 牛客网