一、进程间通信概述
1、目的:
1)数据传输:
一个进程需要将它的数据发送给另一个进程
2)资源共享
多个进程之间共享同样的资源
3)通知事件
一个进程需要向另一个或一组进程发送消息,通知他们发生了某种事件
4)进程控制
有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所以操作,并能够及时知道它的状态改变
2.通信方式:
管道(pipe)和有名管道(FIFO)
信号(signal)
消息队列
共享内存
信号量
套接字(socket)
二、管道通信(默认无名管道)
管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据
无名管道 只有有亲缘关系的进程来使用 因为管道无名很难找到 所以如果父进程创建管道 然后创建子进程 子进程可以继承父进程的变量 才能找到管道
头部与尾部用文件描述符来表示
**数据被一个进程读出后,将被从管道中删除,其他读进程将不能在读到这些数据。**管道提供了简单的流控制机制,进程试图读空管道时,进程将阻塞,同样,管道已满时,进程再试图向管道写入数据,进程将阻塞
1.创建无名管道函数pipe()
头文件是#include<unistd.h>
创建成功返回0 不成功返回-1
读使用read( ) 写使用write( )
上图 需要关闭父进程的读 关闭子进程的写 使它成为单向
2.使用无名管道读取的代码练习
父进程向无名管道写入小写字母 子进程读出后转换为大写字母写入管道 父进程再将其读出 具体代码(可运行)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
#include <ctype.h>
int main(){
int fd[2];
pid_t pid;
char buffer[100];//缓存区
int ret,i;
int ret_num;
memset(buffer,0,sizeof(buffer));//清空缓存区
ret = pipe(fd);//创建无名管道 返回0表示创建成功
if(ret == -1){
perror("pipe error!\n");
exit(-1);
}
pid = fork();//创建子进程
if(pid < 0){//创建失败关闭管道出入口
perror("fork error!\n");
close(fd[0]);
close(fd[1]);
exit(-1);
}
else if(pid > 0){//父进程
if((write(fd[1],"hello",5)) != -1){//向无名管道中写入hello fd【1】是管道写端
printf("father write success!\n");
}
if((write(fd[1],"pipe",5)) != -1){//再次写
printf("father write success!\n");
}
wait(NULL);//等待子进程结束
read(fd[0],buffer,10);//读取管道内容进入缓冲区
printf("father read butter:%s\n",buffer);
close(fd[0]);
close(fd[1]);
}
else{//子进程
if((ret_num = read(fd[0],buffer,10)) != -1){//读取管道中的内容 存放在缓存区 成功 返回读取个数
printf("child:read num is %d , buffer:%s\n",ret_num,buffer);
}
for(i = 0;i < ret_num -1;i++){
buffer[i] = toupper(buffer[i]);//将缓冲区小写转为大写
}
write(fd[1],buffer,ret_num);//将缓冲区内容写入管道
close(fd[0]);
close(fd[1]);
}
return 0;
}
三、命名管道
命名管道和无名管道基本相同,但也有不同点:无名管道只能由有血缘关系的进程使用;但通过命名管道,不相关的进程也能交换数据 因为可以根据名字找到管道 无需继承
1.创建有名管道函数mkfifo()
第一个参数 管道名 其余进程 可通过管道名打开管道
第二个参数 常见属性:S_IRUSE 可读 S_IWUSR可写 S_IXUSR可执行 S_IXRWU 可读可写可执行
int mkfifo(const char * pathname,mode_t mode);
函数说明
mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
1、当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
例 fd = open (FIFO_SEVER,O_RDONLY|O_NONBLOCK);
2、没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回
打开方式 与文件相同 头文件#include<fcntl.c>
fd = open(FIFO_SEVER,O_WRONLY);
2.有名管道 无关系进程之间的读取代码练习
a.写入的代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#include<memory.h>
#include<unistd.h>
#define FIFO_SEVER "/home/2022/0305/fifosever"
int main(){
int fd;
int ret;
char w_buf[1000];
memset(w_buf,0,1000);
if((mkfifo(FIFO_SEVER,O_CREAT|O_EXCL) < 0) && (errno != EEXIST)){
printf("cannt create fifosever!\n");
exit(0);
}
fd = open(FIFO_SEVER,O_WRONLY);
if( fd == -1){
if(errno == ENXIO)
printf("open error,no reading process\n");
}
printf("please input \n");
scanf("%s",w_buf);
ret = write(fd,w_buf,strlen(w_buf));
if(ret == -1){
printf("write fifo error try later!\n");
}
else{
printf("write fifo success ret num is %d\n",ret);
}
return 0;
}
b.读取的代码
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <errno.h>
#include<memory.h>
#include<unistd.h>
#define FIFO_SEVER "/home/2022/0305/fifosever"
int main(){
int fd;
char r_buf[1000];
int ret;
fd = open(FIFO_SEVER,O_RDONLY);
if(fd == -1){
printf("open fifosever fail!\n");
exit(0);
}
memset(r_buf,0,sizeof(r_buf));
ret = read(fd,r_buf,100);
if(ret == -1){
if(errno == EAGAIN)
printf("no data avalible\n");
}
else{
printf("read fifo success! read is %s\n",r_buf);
sleep(1);
}
unlink(FIFO_SEVER);
return 0;
}
四、信号通信
1.信号机制
信号机制是Unix系统中最为古老的进程间通讯机制,很多条件可以产生一个信号:
1.当用户按某些按键时,产生信号
2.硬件异常产生信号:除数为0、无效的存储访问等等。这些情况通常由硬件检测到,将其通知内核,然后内核产生适当的信号通知进程,例如,内核对正在访问一个无效存储区的进程参数一个SIGSEGV信号
3。进程用kill函数将信号发送给另一个进程
4.用户可用kill命令将信号发送给其他进程
2.信号发送kill函数与raise函数
raise默认发给自己
kill第一个参数是指定发生给谁
如果>0 就是指定的进程的ID
=0 把这个信号发生给这个进程所在进程组的每一个进程
<-1 取绝对值 看哪个进程组的ID等于这个绝对值 然后发送信号给这个进程组中所有进程
=-1 发送给除自己以外 所有的进程
当第二个参数 写0 没有信号发送 因为64个信号中没有序号为0的
没有错 意义是检测我有没有权限向指定的pid发送信号
3.alarm函数
注意alarm函数 如果没有给予执行动作的话 默认动作是终止进程
每个进程只能有一个闹钟时间,如果在调用alarm时,以前已为该进程设置过闹钟时间,而且它还没有超时,以前登记的闹钟时间则被新值代换
如果有以前等级的尚未超过的闹钟时间,而这次seconds值是0,则表示取消以前的闹钟
alarm函数可以写多个 但程序运行时 只能有一个生效
并不是只能写一个 可以错峰使用alarm
返回值是没进行完的剩余时间
4.pause函数
5.信号处理:
1.忽略信号
2.执行用户希望的动作
通知内核再某种信号发送时,调用一个用户函数,再用户函数中,执行用户希望的处理
3.执行系统默认动作
对大多数信号的系统默认动作是终止该进程
其中信号集函数组不常用
6.signal信号处理函数
第一个参数 是想捕捉哪个信号
第二个参数是指向函数的指针
所以要传 你想要调用的执行函数的地址(函数名)或者SIG_IGN表示忽略此信号 或者SIG_DFL表示系统默认方式处理(一般系统默认方式 是结束此进程)
7signal处理信号的具体实例代码:
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void func(int signal){
if(signal == SIGINT){//当接收到的信号是SIGINT时 执行printf中的语句
printf("I get SIGNAL!\n");
}
else if(signal == SIGQUIT){//同上
printf("I get SIGQUIT!\n");
}
}
int main(){//使用signal函数 处理信号 执行自己想要的动作 即func函数
printf("waiting for signal SIGINT or SIGQUIT!\n");
signal(SIGINT,func);//当接收到SIGINT信号后 执行func里的动作
signal(SIGQUIT,func);//同上
pause();//进程阻塞 等待一个信号的产生 进程才会终止
return 0;
}
执行结果:
9.signal函数信号处理和kill函数信号发送练习代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<unistd.h>
int main(){
pid_t pid;
int select;
void func(int signal){
if(signal == SIGINT){
printf("I get SIGINT!\n");
}
else if(signal == SIGQUIT){
printf("I get SIGQUIT!\n");
exit(1);
}
}
pid = fork();
if(pid == 0){
signal(SIGINT,func);
signal(SIGQUIT,func);
pause();
}
else{
printf("please input your select 1 or 2\n");
scanf("%d",&select);
if(select == 1){
kill(pid,SIGINT);//向pid这个进程发送ISGINT这个信号
}
else{
kill(pid,SIGQUIT);
}
sleep(1);//让父进程睡1s 防止子进程成孤儿进程
}
return 0;
}
五、共享内存
共享内存是被多个进程共享的一部分物理内存,共享内存是进程间共享数据的一种最快的方法,一个进程向共享内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
1.共享内存使用步骤
1.创建共享内存函数shmget( )
2.映射内核中的共享内存的地址到进程中去 函数为shmat( )
3.使用共享内存 怎么用 直接用
4.有进程不用再使用共享内存 不使用共享内存的进程 用shmdt ( )解除映射
5.所有进程都不用了 有一个进程删除共享内存就可以 使用shmctl( )
2.创建共享内存
key 键值 指向我想创建共享内存的id(相当于给要创建的共享内存的文件名 而返回值是文件描述符)
用于区分不同的共享内存 创建成功后就再也不使用key值了 而使用函数返回的ID值即共享内存标识符。
第二个参数 指定共享内存大小 大小在4k以下 系统都是给4k个字节
第三个参数 表示创建的共享内存的模式 IPC_CREAT不存在就创建
加上|IOC_EXCL表示存在就出错 也可以加权限
3.映射共享内存
第一个参数就是共享内存标识符
第二个参数 是字节地址 是将共享内存的起始地址放在这个参数中 多数情况下写NULL 这是让系统帮我在进程中找个地址存放映射共享内存的地址,而函数返回值 就是这个共享内存映射的地址 以后使用共享内存 就用这个返回值;
第三个参数 取0 表示映射了共享内存在我的进程中是可读可写的
如果映射失败 返回-1 所以以后使用这个函数 不能将返回值直接跟-1比
因为-1是整数 而返回值是char 要将-1 强转为void**
4.解除映射
解除映射函数参数是 映射函数返回值;
5.共享内存代码练习题
父进程向共享内存输入字符 子进程判断是否是自己要接收的信息 是的话 输出共享内存中的信息
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/shm.h>
#include<sys/ipc.h>
#include<sys/wait.h>
#define BUFFER_SIZE 2048
int main(){
pid_t pid;
int shmid;//存放共享内存描述符 类似与文件描述符
char *shm_addr;//用于存放映射共享内存的地址
char flag[] = "parent";//后期将其放入共享内存中 用以区分是否是子进程要接收的信息
char buff[BUFFER_SIZE];//缓存区
if((shmid = shmget(IPC_PRIVATE,BUFFER_SIZE,0666)) < 0){//创建共享内存
perror("shmget!\n");
exit(1);
}
else{
printf("create share memory success!\n");
}
pid = fork();//创建子进程
if(pid < 0){
perror("fork!\n");
exit(1);
}
else if(pid == 0){//子进程
shm_addr = shmat(shmid,NULL,0);//映射共享内存
if(shm_addr == (void*)-1){//判断是否映射成功
perror("shmat!\n");
exit(1);
}
else{
printf("child:Attach share memory success address is:%p\n",shm_addr);//输出映射地址
}
while(strncmp(shm_addr,flag,strlen(flag))){//比较共享内存信息是否是子进程要接收的
printf("child:not my data waiting later...\n");//不等于0的话 睡3s等待
sleep(3);
}
strcpy(buff,shm_addr + strlen(flag));//是为要接收的信息 跳过parent这个字符串 接收其后面的信息
printf("child: share memory:%s\n",buff);//输出共享内存的信息
if(shmdt(shm_addr) < 0){//解除映射
perror("child:shmdt error!\n");
exit(1);
}
else{
printf("child:detached share memory\n");
}
}
else{//父进程
sleep(1);
if((shm_addr = shmat(shmid,NULL,0)) == (void*)-1){//映射共享内存
perror("father:shmat error!\n");
exit(1);
}
else{
printf("father attach share memory is %p\n",shm_addr);//输出映射地址
}
sleep(1);
printf("input string:\n");
fgets(buff,BUFFER_SIZE - strlen(flag),stdin);//输入信息进入缓存区 要去掉parent的大小 因为共享内存空间需要存在parent字符串 不能完全输入缓存区的全部大小
strncpy(shm_addr + strlen(flag),buff,strlen(buff));//给parent字符串留位置 向共享内存输入信息
strncpy(shm_addr,flag,strlen(flag));//向共享内存开头输入parent字符串
if(shmdt(shm_addr) < 0){//解除映射
perror("father:shmdt error!\n");
exit(1);
}
else{
printf("father:detached share memory\n");
}
waitpid(pid,NULL,0);//等待子进程结束
if(shmctl(shmid,IPC_RMID,NULL) == -1){//释放共享内存
perror("shmtlc error!\n");
exit(1);
}
else{
printf("delete share memory\n");
}
}
return 0;
}
6.无血缘关系的进程使用共享内存通信代码练习
将共享内存类型转换为结构体类型 其中两个成员一个是written用以区分是读还是写 还有一个用来存放信息
共享内存结构体
#define TEXT_SZ 2048
struct shared_use_st
{
int written;
char text[TEXT_SZ];
};
写进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#include "shmdata.c"
#include <string.h>
#define BUFFSIZE 2048
int main()
{
int running = 1;
void *shm = NULL;//存放映射共享内存地址
struct shared_use_st * shared = NULL;//定义共享内存结构体类型指针
char buffer[BUFFSIZE + 1];
int shmid;
shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);//有就打开 没有就创建
if(shmid == -1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
shm = shmat(shmid,0,0);// 映射共享内存
if(shm == (void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\n memary attached at %p\n",shm);
shared = (struct shared_use_st *)shm;//将映射地址强转为共享内存结构体类型 并让share指向它
//shared->written = 0;
while(running)
{
while(shared->written == 1)//当wirtten为1时表示是另一个进程正在读共享内存 此时需要等待
{
sleep(1);
printf("waitting...\n");
}
while(running){
printf("Enter some text:\n");
fgets(buffer,BUFFSIZE,stdin);// 向缓存区写入数据
strncpy(shared->text,buffer,TEXT_SZ);//将缓存区的信息写入共享内存
shared->written = 1;//写完 将written改为1 这要另一个进程得到written为1表示它可以读了
if(strncmp(shared->text,"end",3) == 0)//如果输入end 结束循环
running = 0;
}
}
if(shmdt(shm) == -1)//解除映射
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
return 0;
}
读进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/shm.h>
#include "shmdata.c"
#include <string.h>
int main()
{
int running = 1;
void *shm = NULL;
struct shared_use_st * shared;
int shmid;
shmid = shmget((key_t)1234,sizeof(struct shared_use_st),0666|IPC_CREAT);//有1234这个共享内存就打开 没有就创建
if(shmid == -1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
shm = shmat(shmid,0,0);
if(shm == (void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\n memary attached at %p\n",shm);
shared = (struct shared_use_st *)shm;
shared->written = 0;
while(running)
{
if(shared->written != 0)
{
printf("you wrote : %s\n",shared->text);
sleep(rand()%3);
shared->written = 0;//读完将weitten改为0 另一个进程得到written=0 就可以写了
if(strncmp(shared->text,"end",3) == 0)
running = 0;
}
else
sleep(1);
}
if(shmdt(shm) == -1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
if(shmctl(shmid,IPC_RMID,0) == -1)
{
fprintf(stderr,"stmctl failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
return 0;
}
六、消息队列
第一个参数是消息队列的ID即创建时返回值
第二第三个参数是要配合的 第二个参数给出了要开始写的地址 第三个参数表示写多少个字节
第四个参数 是flag 不需要写0(阻塞)
查参数
释放