Linux之进程(二)进程间通信

进程间通信(IPC,InterProcess Communication)  

参考博客: 

是指在不同进程之间传播或交换信息。IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

一、管道(无名管道) 

相当于水管,从上端流入,下端流出,内容被读取后管道就是空的。

1.它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

2.它只能用于父子进程或者兄弟进程之间的通信。

3.它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。父子进程退出后,管道也就消失。

int pipe(int fd[2]);    // 返回值:若成功返回0,失败返回-1

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
        int pipefd[2];
        pid_t pid;
        char writebuf[] = {"hello!"};
        char readbuf[7] = "\0";
        int wstatus;

        if(pipe(pipefd) == 0){
                printf("创建管道成功\n");
        }else{
                printf("创建管道失败!\n");
        }

        if((pid = fork()) < 0){
                printf("创建子进程失败!\n");
        }else if(pid == 0)//子进程
        {
                printf("子进程,pid=%d;",getpid());
                int size_write =  write(pipefd[1], writebuf, strlen(writebuf));
                if(size_write > 0){
                        printf("往管道写入%d字节\n",size_write);
                }
                close(pipefd[1]);
                _exit(1);
        }else if(pid > 0 )//父进程返回子进程进程号
        {
                waitpid(pid,&wstatus,0);//父进程等待子进程退出
                if(WIFEXITED(wstatus)){
                        printf("子进程退出,status=%d\n",WEXITSTATUS(wstatus));
                }
                printf("父进程,pid=%d;",getpid());
                int size_read = read(pipefd[0], readbuf , sizeof(readbuf));
                if(size_read > 0){
                        printf("从管道读取%d字节,内容:%s\n",size_read,readbuf);
                }else{
                        perror("read error");
                }
                close(pipefd[0]);
        }
        return 0;
}

二、命名管道(FIFO)

FIFO,称为命名管道,它是一种文件类型。

1、特点

  1. FIFO可以在无关的进程之间交换数据,与无名管道不同。

  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

2、原型

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

其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

  • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

  • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

创建了个fifo、命名管道,相当于一个文件。两个进程打开这个文件写入内容或者读取内容,一个进程为读打开文件会阻塞直到另外一个进程为写打开文件,这样两个进程就能传递信息。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

//fifo写端
int main()
{
        int fd;
        int size_w;
        char buf[] = "hello !"; ///

        //int mkfifo(const char *pathname, mode_t mode);
        if(mkfifo("./file1",0600) == -1){
                perror("创建fifo失败!");
        }else{
                printf("创建fifo成功\n");
        }

        fd = open("./file1",O_WRONLY);
        if(fd < 0){
                perror("打开fifo失败");
                return -1;
        }

        size_w = write(fd,buf,strlen(buf));
        if(size_w < 0){
                perror("写fifo失败");
        }else{
                printf("往fifo写入%d字节\n",size_w);
        }
        close(fd);
        return 0;
}
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

//fifo写端
int main()
{
        int fd;
        int size_r;
        char buf[8];

        fd = open("./file1",O_RDONLY);
        if(fd < 0){
                perror("打开fifo失败");
                return -1;
        }

        size_r = read(fd,buf,sizeof(buf));
        if(size_r < 0){
                perror("读fifo失败");
        }else{
                printf("从fifo读取%d字节: %s\n",size_r,buf);
        }
        close(fd);
        return 0;
}

三、消息队列

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

1、特点

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

  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

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

2、原型

// 创建或打开消息队列:成功返回队列ID,失败返回-1
1 int msgget(key_t key, int msgflg);
// 添加消息:成功返回0,失败返回-1
2 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
3 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
4 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

1.msgget()  

key: 返回的消息队列id与key的值相关联。

以下两种情况,会创建一个新的消息队列

  • key参数为IPC_PRIVATE
  • 没有与key相对应的消息队列存在,并且flag包含IPC_CREATE

2.msgsnd()

ptr: 指向结构体,结构体格式如下。

struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};

size: 结构体中数组mtext[]的大小,而非整个结构体的大小。

flag: 控制消息队满或者某些特殊情况下的处理方法。如果flag中设置了IPC_NOWAIT标志,函数将立即返回,不发送消息且返回值是-1,如果flag中无IPC_NOWAIT标志,则发送进程挂起以等待队列中腾出可用的空间。一般该参数设置为0。

3.msgrcv()

type: msgrcv() 中type有以下三种情况,为获取消息的优先级:

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

flag: 用于控制消息队列中无指定类型时的处理方法。若flag设置了IPC_NOWAIT标志,函数会立刻返回,返回值是-1,errno 会设置为 ENOMSG。若IPC_NOWAIT标志未被设置,并且无可读的消息,则调用进程被阻塞,直到一条相应类型的消息到达队列。一般情况下,该参数设置为0,无消息可读时,调用线程被阻塞。


步骤:

1.生成key值(ftok()),根据key值打开或创建消息队列(msgget());

2.发送信息(mesgsnd()),程序会阻塞直到另一进程读取消息(msgrcv());

例程

写入端

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[64];    /* message data */
};

//消息队列写端
int main()
{
        int msgid;
        key_t key ;
        struct msgbuf msgbuf1={88,"hello from queue!"};

        if((key=ftok("./",1)) < 0){//生成key值
                perror("ftok error");
                return -1;
        }
        msgid = msgget(key,IPC_EXCL);查看该key对应消息队列是否存在
        if(msgid > 0){
                printf("消息队列已存在,msgid=%d\n",msgid);
        }else{
                msgid = msgget(key,IPC_CREAT|0666);//生成新的消息队列
                if(msgid < 0){
                        printf("创建消息队列失败,errno=%d,[%s]\n",errno,strerror(errno));
                        return -1;
                }else{
                        printf("消息队列创建成功,msgid=%d\n",msgid);
                }
        }

        int res = msgsnd(msgid,&msgbuf1,strlen(msgbuf1.mtext),0);
        if(res < 0){
                perror("msgsnd error");
        }else{
                printf("msgsnd写入成功\n");
        }
        //msgctl(msgid,IPC_RMID,NULL);
        return 0;
}

读取端

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[64];    /* message data */
};

//消息队列读端
int main()
{
        int msgid;
        key_t key;
        struct msgbuf msgbuf1;

        if((key=ftok("./",1)) < 0){
                perror("ftok error");//生成key值
                return -1;
        }
        msgid = msgget(key,IPC_EXCL);//查看该key对应消息队列是否存在
        if(msgid > 0){
                printf("消息队列已存在,msgid=%d\n",msgid);
        }else{
                msgid = msgget(key, IPC_CREAT|0666);//生成新的消息队列
                if(msgid < 0){
                        printf("创建消息队列失败,errno=%d,[%s]\n",errno,strerror(errno));
                        return -1;
                }else{
                        printf("消息队列创建成功,msgid=%d\n",msgid);
                }
        }

        int size_r = msgrcv(msgid,&msgbuf1,sizeof(msgbuf1.mtext),88,0);//0,如果
队列里没有相应type,msgrcv会阻塞
        if(size_r < 0){
                perror("msgrcv error");
        }else{
                printf("读取消息队列成功:msg:%s\n",msgbuf1.mtext);
        }
        msgctl(msgid,IPC_RMID,NULL);//删除消息队列
        return 0;
}

四、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

1、特点

  1. 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

  2. 因为多个进程可以同时操作,所以需要进行同步。

  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2、原型

1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);
4 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

步骤:

A进程写:

1.创建共享内存(shmget());

2.将共享内存映射到当前进程的地址空间(shmat()),用变量指向内存;

3.用strcpy()往共享内存写数据;

4.断开进程与共享内存的连接(shmdt());

B进程读:

1.获取共享内存(shmget());

2.将共享内存映射到当前进程的地址空间(shmat()),用指针指向内存;

3.断开进程与共享内存的连接(shmdt());

4 .删除共享内存(shmctl);

写进程断开与共享内存连接后,在没使用shmclt(shmid,IPC_RMID,0)删除共享内存之前读进程都可以从里面读取,读取后共享内存为空。

例程

写端代码:

/*写端代码*/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define SHM_SIZE 1024*3

int main()
{
        int shmid;
        key_t key;
        char *shmaddr;

        if((key=ftok(".",1)) < 0){//创建key
                perror("ftok error");
                return -1;
        }

        shmid = shmget(key,0,IPC_EXCL);//检查是否有key对应的共享内存
        if(shmid > 0){
                printf("key=%d 对应的共享内存已存在,shmid=%d\n",key,shmid);
        }else{
                shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);//创建新的共享内存
                if(shmid){
                        printf("共享内存创建成功,shmid=%d\n",shmid);
                }else{
                        printf("共享内存创建失败,errno=%d:%s\n",errno,strerror(errno));
                        return -1;
                }
        }

        shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针
        if(shmaddr == (void*)-1){
                perror("shmat error");
                return -1;
        }
        printf("连接共享内存成功\n");

        //向共享内存写入数据
        strcpy(shmaddr,"hello from shm");
        printf("已写入共享内存\n");
        sleep(5);
        shmdt(shmaddr);//断开与共享内存的连接
        return 0;
}

读端代码: 

/*读端代码*/
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define SHM_SIZE 1024*3

int main()
{
        int shmid;
        key_t key;
        char *shmaddr;

        if((key=ftok(".",1)) < 0){//创建key
                perror("ftok error");
                return -1;
        }

        shmid = shmget(key,0,IPC_EXCL);//检查是否有key对应的共享内存
        if(shmid > 0){
                printf("key=%d 对应的共享内存已存在,shmid=%d\n",key,shmid);
        }else{
                shmid = shmget(key,SHM_SIZE,IPC_CREAT|0666);//创建新的共享内存
                if(shmid){
                        printf("共享内存创建成功,shmid=%d\n",shmid);
                }else{
                        printf("共享内存创建失败,errno=%d:%s\n",errno,strerror(errno));
                        return -1;
                }
        }

        shmaddr = shmat(shmid,0,0);//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针
        if(shmaddr == (void*)-1){
                perror("shmat error");
                return -1;
        }
        printf("连接共享内存成功\n");

        //从共享内存读取数据
        printf("共享内存:%s\n",shmaddr);

        shmdt(shmaddr);//断开与共享内存的连接
        shmctl(shmid,IPC_RMID,0);//删除共享内存
        return 0;
}

查看系统中的共享内存

ipcs -m

删除共享内存

ipcrm -m +key

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值