Linux_3:进程间通信

IPC

1.什么是IPC?Inter Process Communication

2.进程间通信常用的几种方式

1,管道通信:有名管道,无名管道

2,信号- 系统开销小

3,消息队列-内核的链表

4,信号量-计数器

5,共享内存

6,内存映射

7,套接字

无名管道

管道的概念

1.本质

内核缓冲区

伪文件-不占用磁盘空间

2特点:

两部分: 读端,写端,对应两个文件描述符

数据写端流入,读端流出

操作管理的进程被销毁之后,管道自动被释放

管道默认是阻塞的

管道的原理

1.内部实现方式:

队列-环形队列

特点:先进先出

2.缓冲区大小

默认4K,大小会根据实际情况做适当调整

管道的局限性

1.队列: 数据只能读取一次,不能重复读取

2.半双工:

单工:遥控器

半双工:对讲机

创建匿名管道

int pipe(int fd[2])

fd‐传出参数:

fd[0]‐读端

fd[1]‐写端

返回值:

0:成功

‐1:创建失败

父子进程使用管道通信

实现 ps aux | grep "bash"

数据重定向:dup2

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        int ret;
        int fd[2];

        ret = pipe(fd);
        if(ret == -1)
        {
                printf("pipe creat failed\n");
                exit(1);
        }
        printf("pipe creat success!\n");

        printf("fd[0] is %d\n",fd[0]);
        printf("fd[1] is %d\n",fd[1]);

        close(fd[0]);
        close(fd[1]);

        return 0;
}
~         

注012是标准输入输出和报错

int dup2(int oldfd, int newfd);//新段指向旧端

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        pid_t pid;
        int ret;
        int fd[2];

        ret = pipe(fd);
        if(ret == -1)
        {
                printf("pipe creat failed\n");
                exit(1);
        }

        printf("pipe creat success!\n");

        pid = fork();
        if(pid == -1)
        {
                printf("fork failed\n");
                exit(1);
        }

        if(pid > 0)
        {
                close(fd[0]);
                dup2(fd[1],STDOUT_FILENO);
                execlp("ps","ps","aux",NULL);
                perror("excelp");
                exit(1);
        }
        else if (pid == 0)
        {
                close(fd[1]);
                dup2(fd[0],STDIN_FILENO);
                execlp("grep","grep","bash","--color=auto",NULL);
        }

        return 0;
}

单个进程也可以使用管道
父子进程在使用管道的时候,父进程写的时候要关闭读,子进程读的时候要关闭写

管道的读写行为

1.读操作

1)有数据:read(fd[0]) 正常读,返回读出的字节数

2)无数据:

写端被全部关闭,read返回0,相当于读文件到了尾部

没有全部关闭,read阻塞

2.写操作

1)读端全部关闭:

管道破裂,进程被终止

内核给当前进程发送信号SIGPIPE-13,默认处理动作

2)读端没全部关闭:

缓冲区写满了,write阻塞

缓冲区没满,write继续写,直到写满,阻塞

3.如何设置非阻塞?

1)默认读写两端都阻塞

2)设置读端为非阻塞pipe(fd):

fcntl-变参函数:复制文件描述符-dup;修改文件属性-open的时候对应flag属性

设置方法

//获取原来的flags

int flags = fcntl(fd[0],F+GETFL);

//设置新的flags

flag |=O_NONBLOCK;

fcntl(fd[0],F_SETFL,flags);

fcntl(fd[0],F_SETFL,flags);

查看管道缓冲区大小

命令:

ulimit -a

fpathconf

long fpathconf(int fd, int name);

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
        int ret;
        int fd[2];

        ret = pipe(fd);
        if(ret == -1)
        {
                printf("pipe creat failed\n");
                exit(1);
        }
        printf("pipe creat success!\n");

        long size = fpathconf(fd[0],_PC_PIPE_BUF);
        printf("size id %ld\n",size);

        printf("fd[0] is %d\n",fd[0]);
        printf("fd[1] is %d\n",fd[1]);

        close(fd[0]);
        close(fd[1]);

        return 0;
}

有名管道

函数形式:int mkfifo(const char \*filename,mode_t mode);

功能:创建管道文件

参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。

返回值:创建成功返回0,创建失败返回-1。

特点

有名管道

在磁盘上有这样一个文件 ls -l ->p

也是一个伪文件,在磁盘大小永久为0

数据存在内核中有一个对应的缓冲区

半双工通信方式

使用场景

没有血缘关系的进程间通信

创建方式

命令:mkfifo 管道名

函数:mkfifo()

int mkfifo(const char *pathname, mode_t mode);

fifo文件可以使用io函数进程操作

open/close,read/write

不能执行lseek操作

读函数

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
        int ret;
        int fd;
        int nread;
        char readBuff[50] = {0};

        ret = mkfifo("/home/u/process/myfifo",0777);

        if(ret == -1)
        {
                return -1;
        }

        printf("creat file success!\n");

        fd = open("./myfifo",O_RDONLY);
        if(fd < 0)
        {
                return -1;
        }
        printf("open file success!\n");

        nread = read(fd,readBuff,50);
        printf("read %d byte from fifo :%s\n",nread,readBuff);

        close(fd);
        return 0;
}

写程序

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main()
{

        int fd;
        char *str = "hello world!";

        fd = open("./myfifo",O_WRONLY);
        if(fd < 0)
        {
                return -1;
        }
        printf("open file success!\n");

        write(fd,str,strlen(str));

        close(fd);

        return 0;
}
~      

消息队列

消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识。

特点

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在

消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。

相关函数

int msgget(key_t key, int msgflg);

//创建或打开消息队列,

参数:

key:和消息队列关联的key值

msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。

msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

返回值:

成功返回队列ID,失败则返回‐1,

在以下两种情况下,msgget将创建一个新的消息队列:

如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志

key参数为IPC_PRIVATE
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);

//读取消息,成功返回消息数据的长度,失败返回‐1

参数:

msgid:消息队列的ID

msgp:指向消息的指针,常用结构体msgbuf如下:

struct msgbuf

{

long mtype; //消息类型

char mtext[N]; //消息正文

}

size:发送的消息正文你的字节数

flag:

IPC_NOWAIT 消息没有发送完成函数也会立即返回

0:知道发送完成函数才返回

返回值:

成功:0

失败:‐1
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

//从一个消息队列中获取消息

参数:

msgid:消息队列的ID

msgp:要接收消息的缓冲区

size:要接收的消息的字节数

msgtype:

0:接收消息队列中第一个消息

大于0:接收消息队列中第一个类型为msgtyp的消息

小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。

flag:

0:若无消息函数一直阻塞

IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。

返回值:

成功:接收到的消息i长度

出错:‐1

函数msgrcv在读取消息队列时,type参数有下面几种情况

type ==0,返回队列中的第一消息

type >0,返回队列中消息队列类型为type的第一个消息

type <0,返回队列中消息类型值小于或等于type绝对值的消息,如果有多个,则取类型值最小的消息。

可以看出,type值非0时用于以非先进先出次序读取消息,也可以把type看成优先级的权值
msgctl(int msqid, int cmd, struct msqid_ds *buf);

//控制消息队列,成功返回0,失败返回‐1 参数:

msgid:消息队列的队列ID

cmd:

IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。

IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值

IPC_RMID:删除消息队列

buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构

返回值:

成功:0

失败:‐1
ftok函数

key_t ftok( char * fname, int id )

//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

参数:

fname就是你指定的文件名(该文件必须是存在而且可以访问的)。

id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。

返回值:

当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。

消息队列建立和删除

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>

int main()
{
        int msgid;

        msgid = msgget(IPC_PRIVATE,0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        printf("creat message queue success,msgid = %d\n",msgid);

        system("ipcs -q");

        msgctl(msgid,IPC_RMID,NULL);

        system("ipcs -q");

        return 0;
}

单进程使用消息队列

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
};

int main()

{
        int msgid;
        struct msgbuf sendbuf,readbuf;
        int readret;

        msgid = msgget(IPC_PRIVATE,0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        system("ipcs -q");
        printf("creat message queue success,msgid = %d\n",msgid);

        //init msgbuf
        sendbuf.mtype = 100;
        printf("plese input message:\n");
        fgets(sendbuf.mtext,128,stdin);

        //send message to message queue
        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);

        //read message
        memset(readbuf.mtext,0,128);
        readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
        printf("receive mesagge is :%s\n",readbuf.mtext);
        printf("total message is %d\n",readret);

        return 0;
}

读出内容的时候,节点中内容被删除,但是节点仍然存在

消息队列进程间通信

写进程

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
        char ID[4];
};

int main()
{
        struct msgbuf sendbuf;
        int msgid;
        int readret;
        key_t key;

        key = ftok("a.c",1);

        msgid = msgget(key,IPC_CREAT | 0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        system("ipcs -q");
        printf("creat message queue success,msgid = %d\n",msgid);

        //init message
        sendbuf.mtype = 100;

        //send message to message queue
        while(1)
        {
                memset(sendbuf.mtext,0,128);
                printf("please input to message queque\n");
                fgets(sendbuf.mtext,128,stdin);
                msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
        }

        return 0;

}
    

读进程

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
        char ID[4];
};

int main()
{
        struct msgbuf readbuf;
        int msgid;
        int readret;
        key_t key;

        key = ftok("a.c",1);

        msgid = msgget(key,IPC_CREAT | 0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        system("ipcs -q");
        printf("creat message queue success,msgid = %d\n",msgid);

        //init message
        readbuf.mtype = 100;

        //send message to message queue
        while(1)
        {
                memset(readbuf.mtext,0,128);
                readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
                printf("receive message id %s\n",readbuf.mtext);
                printf("total receive %d byte\n",readret);
        }

        return 0;

}

消息队列进程间全双工通信

service进程

cs 复制代码
int main()
{
        struct msgbuf sendbuf,readbuf;
        int msgid;
        int readret;
        int pid;
        key_t key;

        key = ftok("a.c",1);

        msgid = msgget(key,IPC_CREAT | 0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        system("ipcs -q");
        printf("creat message queue success,msgid = %d\n",msgid);

        //init message
        sendbuf.mtype = 100;

        pid = fork();

        if(pid > 0)//parent progress write 100
        {
                while(1)
                {
                        memset(sendbuf.mtext,0,128);
                        printf("please input to message queque\n");
                        fgets(sendbuf.mtext,128,stdin);
                        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
                }
        }
        else if(pid == 0)//child progress read 200
        {
                while(1)
                {
                        memset(readbuf.mtext,0,128);
                        readret = msgrcv(msgid,(void *)&readbuf,128,200,0);
                        printf("receive progress is %s\n",readbuf.mtext);
                        printf("total receive %d byte\n",readret);
                }
        }

        return 0;

}

client进程

cs 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct msgbuf
{
        long mtype;
        char mtext[128];
        char ID[4];
};

int main()
{
        struct msgbuf sendbuf,readbuf;
        int msgid;
        int readret;
        int pid;
        key_t key;

        key = ftok("a.c",1);

        msgid = msgget(key,IPC_CREAT | 0755);
        if(msgid == -1)
        {
                printf("creat message queue failed\n");
                return -1;
        }
        system("ipcs -q");
        printf("creat message queue success,msgid = %d\n",msgid);

        //init message
        sendbuf.mtype = 200;

        pid = fork();

        if(pid == 0)//child progress write 200
        {
                while(1)
                {
                        memset(sendbuf.mtext,0,128);
                        printf("please input to message queque\n");
                        fgets(sendbuf.mtext,128,stdin);
                        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtext),0);
                }
        }
        else if(pid > 0)//parent progress read 200
        {
                while(1)
                {
                        memset(readbuf.mtext,0,128);
                        readret = msgrcv(msgid,(void *)&readbuf,128,100,0);
                        printf("receive progress is %s\n",readbuf.mtext);
                        printf("total receive %d byte\n",readret);
                }
        }

        return 0;

}

注意:消息类型一一对应就可以,父进程读还是写取决于你对它业务的安排,你安排它读就让它读

共享内存

概念

共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。

相关函数

int shmget(key_t key, size_t size, int shmflg);

//用来获取或创建共享内存

参数:

key:IPC_PRIVATE或ftok的返回值

size:共享内存区大小

shmflg:同open函数的权限位,也可以用8进制表示法

返回值:

成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符

出错:‐1
void *shmat(int shm_id, const void *shm_addr, int shmflg);

//把共享内存连接映射到当前进程的地址空间

参数:

shm_id:ID号

shm_addr:映射到的地址,NULL为系统自动完成的映射

shmflg:SHM_RDONLY共享内存只读。默认是0,表示共享内存可读写

返回值:

成功:映射后的地址

失败:NULL
int shmdt(const void *shmaddr);

//将进程里的地址映射删除

参数:

shmid:要操作的共享内存标识符

返回值:

成功:0

出错:‐1
int shmctl(int shm_id, int command, struct shmid_ds *buf);

//删除共享内存对象

参数:

shmid:要操作的共享内存标识符

cmd : IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m IPC_SET (设置对象属性) IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m shmid

buf :指定IPC_STAT/IPC_SET时用以保存/设置属性

返回值:

成功:0

出错:‐1

特点:

1.共享内存创建之后,一直存在于内核中,直到被删除或系统关闭

2.共享内存和管道不一样,读取后,内容仍然在共享内存中

共享空间创建和读取

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
        int shmid;
        int key;
        char *p;

        key = ftok("a.c",0);
        if(key < 0)
        {
                printf("ftok failure\n");
                return -1;
        }
        printf("ftok success,key:%d\n",key);

        shmid = shmget(key,128,IPC_CREAT | 0777);
        if(shmid < 0)
        {
                printf("creat share memory failure\n");
                return -2;
        }
        printf("creat share memory success,shmid is %d\n",shmid);
        system("ipcs -m");

        p = (char *)shmat(shmid,NULL,0);
        if(p ==NULL)
        {
                printf("shmat function failure!\n");
                return -3;
        }

        //write to share memory
        fgets(p,128,stdin);

        //read from share memory
        printf("read share memory date is %s\n",p);
        printf("read second share memory is %s\n",p);

        return 0;
}

注:fgets(p, 128, stdin) 的作用是从 标准输入(stdin) 读取最多 128 个字符(包括换行符),并将其 写入共享内存(p 指向的共享内存区域)

删除共享内存映射

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
        int shmid;
        int key;
        char *p;

        key = ftok("a.c",0);
        if(key < 0)
        {
                printf("ftok failure\n");
                return -1;
        }
        printf("ftok success,key:%d\n",key);

        shmid = shmget(key,128,IPC_CREAT | 0777);
        if(shmid < 0)
        {
                printf("creat share memory failure\n");
                return -2;
        }
        printf("creat share memory success,shmid is %d\n",shmid);
        system("ipcs -m");

        p = (char *)shmat(shmid,NULL,0);
        if(p ==NULL)
        {
                printf("shmat function failure!\n");
                return -3;
        }

        //write to share memory
        fgets(p,128,stdin);

        //read from share memory
        printf("read share memory date is %s\n",p);
        printf("read second share memory is %s\n",p);

        shmdt(p);
        memcoy(p,"hello",NULL);

        return 0;
}

此时删掉了映射地址空间,再往里面写东西会发生段错误。mencpy()

删除共享内存

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main()
{
        int shmid;
        int key;
        char *p;

        key = ftok("a.c",0);
        if(key < 0)
        {
                printf("ftok failure\n");
                return -1;
        }
        printf("ftok success,key:%d\n",key);

        shmid = shmget(key,128,IPC_CREAT | 0777);
        if(shmid < 0)
        {
                printf("creat share memory failure\n");
                return -2;
        }
        printf("creat share memory success,shmid is %d\n",shmid);
        system("ipcs -m");

        p = (char *)shmat(shmid,NULL,0);
        if(p ==NULL)
        {
                printf("shmat function failure!\n");
                return -3;
        }

        //write to share memory
        fgets(p,128,stdin);

        //read from share memory
        printf("read share memory date is %s\n",p);
        printf("read second share memory is %s\n",p);

        shmdt();
        shmctl(shmid,IPC_RMIC,NULL);
        system("ipcs -m");

        return 0;
}

封装ipcrm -m shmid

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>

int main(int argc,char *argv[])
{
        int shmid;

        if(argc < 3)
        {
                printf("input error\n");
                return -1;
        }

        if(strcmp(argv[1],"-m") == 0)
        {
                printf("delete share memory\n");
        }
        else
        {
                return -2;
        }

        shmid = atoi(argv[2]);
        printf("delete share memory id :%d\n",shmid);
        shmctl(shmid,IPC_RMID,NULL);

        system("ipcs -m");

        return 0;
}
~    

注:atoi() asc码转int类型

相关推荐
晚风_END18 分钟前
Linux|服务器|二进制部署nacos(不是集群,单实例)(2025了,不允许还有人不会部署nacos)
linux·运维·服务器·数据库·编辑器·个人开发
阿沁QWQ39 分钟前
应用层协议和JSON的使用
运维·服务器·网络
运维开发王义杰1 小时前
不止于监控:深入剖析OpenTelemetry的可观察性生态体系
运维
LCG元1 小时前
基于MCP的CI/CD流水线:自动化部署到云平台的实践
运维·ci/cd·自动化
I'mSQL1 小时前
C#与FX5U进行Socket通信
运维·服务器·自动化·wpf
Gene_20222 小时前
[TOOL] ubuntu 使用 ffmpeg 操作 gif、mp4
linux·ubuntu·ffmpeg
Fanmeang2 小时前
OSPF与BGP的联动特性实验案例
运维·网络·华为·ospf·bgp·路由黑洞·ospf联动bgp
哈哈浩丶2 小时前
Linux驱动开发2:字符设备驱动
linux·运维·驱动开发
啊森要自信2 小时前
【Linux 学习指南】网络基础概念(一):从协议到分层,看透计算机通信的底层逻辑
linux·运维·服务器·网络·网络协议·tcp/ip·ip
asdfg12589632 小时前
策略路由Policy-Based Routing(PBR)
linux·网络·wireshark·网络工程·策略路由