进程间通信

无名管道 pipe

管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。

创建管道函数
#include <unistd.h>
int pipe(int fd[2]);

在这里插入图片描述
单个进程中的管道几乎没有任何用处。通常,进程会先调用 pipe,接着调用 fork,从而创建从父进程到子进程的IPC通道

管道读写

fork之后做什么取决于我们想要的数据流的方向。对于从父进程到子进程的管道,父进程关 闭管道的读端(fd[0]),子进程关闭写端(fd[1])
在这里插入图片描述
示例代码

#include <stdio.h>
#include <unistd.h>

int main()
{
    int fd[2];
    pid_t pid;
    char buff[16];
    //创建管道
    if(pipe(fd) < 0){
        perror("fail pipe create :");
        return -1;
    }
    //创建子进程
    if((pid = fork()) < 0){
        perror("fail fork: ");
        return -1;
    }
    else if (pid == 0){
        //子进程关闭写端,开始接受父进程消息
        close(fd[1]);
        read(fd[0], buff, 15);
        fputs(buff, stdout);
    }
    else {
        //父进程关闭读端,开始写入信息
        close(fd[0]);
        write(fd[1] ,"Hello Pipe!\n" ,16);
    }


    return 0;
}

运行结果
在这里插入图片描述

命名管道 FIFO

有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信

创建FIFO
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);

返回值:成功返回0; 失败返回-1,可以通过errno判断错误类型

  • pathname:要创建的FIFO文件名且该文件必须不存在

  • mode:指定FIFO文件的权限(8进制数,如0664)

打开FIFO

一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。

int fd;
fd = open(FIFO , O_WRONLY)

FIFO文件
在这里插入图片描述

管道文件读写

写入管道消息

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

#define FIFO "file"

int main()
{
    //创建管道,不提醒已经存在的错误
    if(mkfifo(FIFO,0600) < 0 && errno != EEXIST)
    {
        perror("FIFO make:");
        return -1;
    }

    int fd;
    int cnt=10;
    //以只读的权限打开管道文件
    if((fd = open(FIFO , O_WRONLY)) < 0)
    {
        perror("open FIFO:");
        return -1;
    }
    //多次循环写入内容
    while(cnt--)
    {
        write(fd , "hello\n" ,16);
        sleep(1);
    }

    close(fd);
    return 0;
}

读取管道消息

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

#define FIFO "file"

int main()
{
	//创建管道文件,如果已经存在不提示
    if(mkfifo(FIFO , 0600) == -1) {
        if(errno == EEXIST){
            printf("FIFO already have\n");
        }
        else {
            perror("mkfifo :");
            return -1;
        }
    }
    else{
        printf("FIFO successfully!\n");
    }
	
    char buff[16];
    int nread=0;
	//打开管道文件
    int fd = open(FIFO, O_RDONLY);
    while(1)
    {
    	//循环读取,输出到终端
        nread = read(fd, buff, 15);
        fputs(buff,stdout);
    }
    close(fd);

    return 0;
}

运行结果
在这里插入图片描述

消息队列 MessageQueue

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

消息队列的数据格式

无论是发送还是接收消息,都有固定的格式。

struct msgstru
{
    long msgtype; //消息类型
    // 消息内容
};

消息结构体中,首位必须是 (long类型)消息类型 , 内容可以按需求定义,可以是数组,也可以是结构体

//文本类型
struct msgstru{
    long msgtype;
    char text[20];
};
//整型
struct msgstru{
    long msgtype;
    int start;
};
//结构体
typedef struct {
    char name[20];
    int age;
}Person;
struct msgstru{
    long msgtype;
    Person person;
};
创建和获取 ipc 内核对象
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
  • 参数key:相当于文件系统的文件名
  • 参数msgflg:是一个操作指令
    当想要创建一个消息队列时可以填IPC_CREAT | 0644,前面指的是创建的指令,后面的0644指的是消息队列的执行权限。
  • 返回值:ipc 内核对象 id

key值可以使用 ftok() 函数获取

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
  • 参数pathname:指定的文件名,这个文件必须是存在的而且可以访问的。
  • id是子序号,它是一个8bit的整数。即范围是0~255。当函数执行成功,则会返回key_t键值,否则返回-1。
    在一般的UNIX中,通常是将文件的索引节点取出,然后在前面加上子序号就得到key_t的值。
发送数据
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  • 参数 msqid:ipc 内核对象 id

  • 参数 msgp:消息数据地址

  • 参数 msgsz:消息正文部分的大小(不包含消息类型)

  • 参数 msgflg:可选项
    该值为 0:如果消息队列空间不够,msgsnd 会阻塞。
    IPC_NOWAIT:直接返回,如果空间不够,会设置 errno 为 EAGIN.

  • 返回值:0 表示成功,-1 失败并设置 errno。

接收数据
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
  • 参数 msqid:ipc 内核对象 id
  • 参数 msgp:用来接收消息数据地址
  • 参数 msgsz:消息正文部分的大小(不包含消息类型)
  • 参数 msgtyp:指定获取哪种类型的消息
  • 参数 msgflg:
    如果为 0 表示没有消息就阻塞。
    IPC_NOWAIT:如果指定类型的消息不存在就立即返回,同时设置 errno 为 ENOMSG
    MSG_EXCEPT:仅用于 msgtyp > 0 的情况。表示获取类型不为 msgtyp 的消息
    MSG_NOERROR:如果消息数据正文内容大于 msgsz,就将消息数据截断为 msgsz
  • msgrcv 函数从消息队列取出消息后,并将其从消息队列里删除。
获取和设置消息队列的属性
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msqid:消息队列标识符
cmd:控制指令
IPC_STAT:获得msgid的消息队列头数据到buf中
IPC_SET:设置消息队列的属性,要设置的属性需先存储在buf中,可设置的属性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes
buf:消息队列管理结构体。
返回值:
成功:0
出错:-1,错误原因存于error中

示例代码
接收

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


struct msgstru
{
    long msgtype;
    char msgtext[64];
};

int main()
{
    key_t  key;
    struct msgstru msgr;
    struct msgstru msgs;
    int msgPid;
    int nrecv=0;

    key = ftok(".",1);

    if((msgPid = msgget(key,IPC_CREAT|0666)) < 0)
    {
        perror("msgGet:");
        return -1;
    }

    printf("MSGKEY : %x\n",key);

    msgs.msgtype = 999;
    strcpy(msgs.msgtext, "msgrcv send message!");
    while(1){
        nrecv = msgrcv(msgPid, &msgr, sizeof(msgr.msgtext), 666, 0);
        printf("recv type : %ld\nmsg recv: %s, total %d bytes\n",msgr.msgtype, msgr.msgtext,nrecv);

        msgsnd(msgPid, &msgs, strlen(msgs.msgtext), 0);

    }
    msgctl(msgPid, IPC_RMID, 0);

    return 0;
}

发送

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



struct msgstru
{
    long msgtype;
    char msgtext[32];
};

int main()
{
    key_t key;
    int msgqid;
    int nsend;
    struct msgstru msgs;
    struct msgstru msgr;

    key = ftok(".",1);

    if((msgqid = msgget(key, IPC_CREAT|0666)) < 0)
    {
        perror("msgGet:");
        return -1;
    }

    msgs.msgtype = 666;
    strcpy(msgs.msgtext, "msg send message!");

    for(;;)
    {
        nsend = msgsnd(msgqid, &msgs, strlen(msgs.msgtext), 0);

        msgrcv(msgqid, &msgr, sizeof(msgr.msgtext), 999, 0);
        printf("message type: %ld\ntext: %s\n",msgr.msgtype, msgr.msgtext);
        sleep(1);
    }

    
    msgctl(msgqid, IPC_RMID, 0);
    return 0;
}

运行结果
在这里插入图片描述

共享存储 SharedMemory

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

共享内存并未提供锁机制,也就是说,在某一个进程对共享内存的进行读写的时候,不会阻止其它的进程对它的读写。如果要对共享内存的读/写加锁,可以使用信号灯。

获取或创建共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
  • 参数key 是共享内存的键值,是一个整数,typedef unsigned int key_t,是共享内存在系统中的编号,不同共享内存的编号不能相同,这一点由程序员保证。key用十六进制表示比较好。

  • 参数size 是待创建的共享内存的大小,以字节为单位。

  • 参数shmflg 是共享内存的访问权限,与文件的权限一样,0666|IPC_CREAT表示全部用户对它可读写,如果共享内存不存在,就创建一个共享内存。

连接共享内存
void *shmat(int shm_id, const void *shm_addr, int shmflg);
  • 参数shm_id 是由shmget函数返回的共享内存标识。

  • 参数shm_addr 指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。

  • 参数shm_flg 是一组标志位,通常为0。

  • 成功时返回共享内存的地址,失败返回-1

断开共享内存
int shmdt(const void *shmaddr);
  • 参数shmaddr 共享内存的地址。

  • 调用成功时返回0,失败时返回-1.

删除共享内存
int shmctl(int shm_id, int command, struct shmid_ds *buf);
  • 参数shm_id 是共享内存标识符。

  • 参数command IPC_RMID代表删除,还有其他参数。

  • 参数buf 可以选择默认,0。

示例代码

写入

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SIZE 1024
int main()
{
    int shmid;
    char *shmaddr;
    char buff[SIZE];
    key_t KEY;
    //获取 KEY 值
    KEY = ftok(".",1);
	//创建共享内存,得到共享内存的标识符
    if((shmid = shmget(KEY, SIZE, 0600|IPC_CREAT)) < 0){
        perror("shmget:");
        return -1;
    }
	//挂载共享内存,获取该地址
    shmaddr = (char *)shmat(shmid, 0, 0);
    //循环写入
    printf("写入消息到共享内存中(输入 quit 退出):\n");
    while(1)
    {
        fgets(buff, SIZE, stdin);
        strcpy(shmaddr,buff);

        if(strcmp(buff,"quit\n") == 0){
            break;
         }
        memset(buff, '\0', SIZE);
    }

    printf("进程退出\n");
    //卸载
    shmdt(shmaddr);
	//删除
    shmctl(shmid, IPC_RMID, 0);

    return 0;
}

读取

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SIZE 1024

int main()
{
    int shmid;
    char *shmaddr;
    char buff[SIZE];
    key_t KEY;
	//获取 KEY 值
    KEY = ftok(".",1);
    //获取共享内存,得到共享内存的标识符
    if((shmid = shmget(KEY, SIZE, 0)) < 0){
        perror("shmget:");
        return -1;
    }
	//挂载共享内存,获取该地址
    shmaddr = (char *)shmat(shmid, 0, 0);
    printf("开始接收共享内存的消息 \n");
	//循环读取,判断并打印
    while(strcmp(buff, "quit\n") != 0)
    {
        if(strcmp(shmaddr, buff) != 0 && strlen(shmaddr) > 0){
            printf("get shm message: %s", shmaddr);
            strcpy(buff, shmaddr);
        }
    }
    //卸载共享内存
    shmdt(shmaddr);
    printf("进程退出\n");
    return 0;
}

运行结果
在这里插入图片描述

共享内存相关命令
查看
ipcs -m

在这里插入图片描述

删除
ipcrm -m <shmid>

在这里插入图片描述

进程间信号量

信号量可以用来进程间的同步
在共享内存中,没有进行同步互斥的功能,而信号量的功能就是实现同步和互斥
在共享内存中的示例代码中,可以看出有明显的不足,关于消息的循环读取会严重的占用CUP,这时候就需要信号量来同步消息的发送和接收
进程间的信号量与线程的信号量的使用方法类似,进程间的称为 <有名信号量>

创建有名信号量
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

  • 参数name 信号量的名字,两个不同的进程通过同一个名字来进行信号量的传递。

  • 参数oflag 当他是O_CREAT时,如果name给出的信号量不存在就创建,此时必须给出mode和vaule。当O_CREAT和O_EXCL同时存在时,如果已存在该信号量,就报错。0为默认。

  • 参数mode 指定信号量的权限。

  • 参数vaule 信号量的初始值。

  • 返回值sem_t 是一个结构,如果函数调用成功,则返回指向这个结构的指针,里面装着当前信号量的资源数,如果失败则返回SEM_FAILED

创建的信号量默认放在 /dev/shm/ 目录下

信号量操作
P操作
int sem_wait(sem_t *sem);

如果有可用的资源(信号量>0),那么占用一个资源(信号量-1)。如果没有可用的资源(信号量=0),则进程被阻塞,直到系统重新给他分配资源。

V操作
sem_post(sem_t *sem)

如果在该信号量的等待队列中有进程在等待该资源,则唤醒一个进程,否则释放一个资源(信号量+1)

进程间的信号量的P/V操作与线程中的使用方法类似

关闭有名信号量
#include <semaphore.h>

int sem_close(sem_t *sem);

与线程中不同,进程间需要先关闭信号量,再删除

删除有名信号量
#include <semaphore.h>

int sem_unlink(const char *name);

当使用完有名信号后,需要调用函数sem_unlink来释放资源。

示例代码
在上文中共享内存中加入信号量
写入

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <semaphore.h>
#include <errno.h>
#include <fcntl.h>

#define SIZE 1024
#define SEM_NAME "sem_text"
int main()
{
    int shmid;
    char *shmaddr;
    char buff[SIZE];
    key_t KEY;
    sem_t *semid;
    //创建有名信号量
    if((semid = sem_open(SEM_NAME, O_CREAT, 0666, 0)) == SEM_FAILED){
        perror("sem_open");
        return -1;
    }
    KEY = ftok(".",1);
    if((shmid = shmget(KEY, SIZE, 0600|IPC_CREAT)) < 0){
        perror("shmget:");
        return -1;
    }

    shmaddr = (char *)shmat(shmid, 0, 0);
    
    printf("写入消息到共享内存中(输入 quit 退出):\n");
    while(strcmp(buff, "quit\n") != 0)
    {
        memset(buff, '\0', SIZE);
        fgets(buff, SIZE, stdin);
        strcpy(shmaddr,buff);
        //将内存写到共享内存后进行V操作,开始同步读的进程
        sem_post(semid);
    }

    printf("进程退出\n");
    //关闭并销毁信号量
    sem_close(semid);
    sem_unlink(SEM_NAME);
    shmdt(shmaddr);

    shmctl(shmid, IPC_RMID, 0);

    return 0;
}

读取

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <semaphore.h>

#define SIZE 1024
#define SEM_NAME "sem_text"

int main()
{
    int shmid;
    char *shmaddr;
    char buff[SIZE];
    key_t KEY;
    sem_t *semid;
	//获取有名信号量的地址
    if((semid = sem_open(SEM_NAME, 0)) == SEM_FAILED){
        perror("sem_open:");
        return -1;
    }

    KEY = ftok(".",1);
    if((shmid = shmget(KEY, SIZE, 0)) < 0){
        perror("shmget:");
        return -1;
    }

    shmaddr = (char *)shmat(shmid, 0, 0);
    printf("开始接收共享内存的消息 \n");

    while(strcmp(buff, "quit\n") != 0)
    {
    	//P操作,接收同步信号
        sem_wait(semid);
        printf("get shm message: %s", shmaddr);
        strcpy(buff, shmaddr);
    }
    //关闭信号量
    sem_close(semid);
    shmdt(shmaddr);
    printf("进程退出\n");
    return 0;
}

运行结果
当程序运行时,可以在 /dev/shm/ 目录下看到该信号量
在这里插入图片描述
在程序推出后,该信号量被销毁

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值