【Linux】进程间通信(IPC)

进程间通信方法:管道(命名管道),共享内存区,信号量,消息队列,套接字

管道

管道在内存中分配空间,如果断电则其中的数据就不在了。

必须有至少一个读和一个写

对管道的操作只有只读或只写两种方式,没有读写——这种形式是未定义的

如果程序中只有读或只有写则程序会被阻塞住。

有名管道(fifo)

有名管道可以在任意两个进程间通信,有名字

fifo文件的大小永远为0

A程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int fdw = open("fifo",O_WRONLY);//打开fifo管道,只写,fdw为文件描述符
    if(fdw == -1)exit(1);//打开失败
    printf("fdw=%d\n",fdw);
    while(1)//输入数据
    {
        printf("input:\n");
        char buff[128]={0};//创建一个空间
        fgets(buff,128);//存放输入的东西

        if(strncmp(buff,"end",3)==0)break;//如果前三个字符为end则退出输入
        write(fdw,buff,strlen(buff));//写
    }
    close(fdw);
    exit(0);
}

B程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int fdr = open("fifo",O_RDONLY);//打开fifo管道,只读形式
    if(fdr == -1)exit(1);//打开失败,退出

    printf("fdr==%d\n",fdr);
    while(1)//一直读
    {
        char buff[128]={0};//创建一个空间
        int n = read(fdr,buff,127);//读文件描述符fdr指向的文件的内容到buff中
        if(n==0)break;//当读不到时退出程序
        printf("read(%d):%s",n,buff);
    }
    close(fdr);
    exit(0);
}

写数据的一端一旦关闭,则读数据的一端也会被关闭,读的返回值为0。

读关闭,写产生异常,产生信号(SIGPIPE)13

要一起运行ab程序管道才能用,否则会被阻塞,可以开两个终端使用。


无名管道(pipe)

无名管道只能在父子进程间通信

管道的通信方式:半双工,允许二台设备之间的双向数据传输,但不能同时进行

其他的通信方式:

单工:不能同时收发,数据传输只支持数据在一个方向上传输

全双工:收发可以同时进行

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
   int fd[2];//fd[0] r,fd[1] w
    if(pipe(fd) == -1)//创建管道失败
    {
        printf("pipe err\n");
        exit(1);
    }
    pif_t pid = fork();//复制进程
    if(pid == -1)//复制失败
    {
        printf("fprk err\n");
        exit(1);
    }
    if(pid == 0)//子
    {
        close(fd[1]);//半双工,关闭写,下面要读
        char buff[128] = {0};
        read(fd[0],buff,127);
        printf("child buff=%s\n",buff);
    }
    else//父
    {
        close(fd[0]);//关闭读
        write(fd[1],"hello",5);//将hello写入fd[1]
        close(fd[1]);
    }
    exit(0);
}

管道的实现和顺序队列类似


信号量(sem)

信号量表示可用资源的数目,主要用来同步进程。

信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量;

pv操作:

可以进行原子操作,+1(v操作)释放资源,-1 (p操作)获取资源(用一个资源,资源减少)

一般取正数值,0代表资源没有了,p操作会被阻塞;

临界资源:同一时刻,只允许被一个进程或线程访问的资源(一个用完另外一个才能用)

临界区:访问临界资源的代码段

a程序:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    for(int i=0;i<5;i++)
    {
        printf("A");
        fflush(stdout);//刷屏

        int n = rand()%3;//模拟做了一会其他的东西再继续
        sleep(n);

        printf("A");
        fflush(stdout);

        n = rand()%3;
        sleep(n);
    }
}

b程序:把a程序的输出A改成B

如果没有pv操作,会变成这样AB交叉输出,ab程序同时使用资源:

封装信号量

创建、获取已存在的信号量:semget()

对信号量的值加减一semop()

赋初始值、销毁semctl()

失败返回负一

首先:sem.h头文件声明一下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/sem.h>
//需要用这个联合体内的val初始化信号量的值
union semun
{
    int val;
    struct semid_ds*buf;
    unsigned short *array;
    struct seminfo *__buf;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();

实现pv操作的封装

不同的进程使用相同的key值可以获取同一个信号量

#include"sem.h"

static int semid = -1;//信号量
void sem_init()
{
    semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建一个信号量
    if(semid==-1)//全新创建失败
    {//获取以前的
        semid = semget((key_t)1234,1,0600);
        //如果还是获取失败就是失败了
        if(semid==-1)
        {
            printf("semget err\n");
        }
        else//初始化
        {
            union semun a;
            a.val = 1;
            if(semctl(semid,0,SETVAL,a)==-1)
            {
                printf("semctl setval err\n");
            }
        }
    }   
}
void sem_p()
{
    struct sembuf buf;
    buf.sem_num = 0;//下标
    buf.sem_op = -1;//p操作
    buf.sem_flg = SEM_UNDO;//防止程序崩溃,崩溃自动执行v操作

    if(semop(semid,&buf,1)==-1)//找semid第0个元素-1
    {
        printf("op p err\n");
    }
}
void sem_v()
{
    struct sembuf buf;
    buf.sem_num = 0;//下标
    buf.sem_op = 1;//v操作

    if(semop(semid,&buf,1)==-1)
    {
        printf("op v err\n");
    }
}

void sem_destroy()
{
    if(semctl(semid,0,IPC_RMID)==-1)
    {
        printf("semctl rm err");
    }
}

将封装实现好的pv操作加到程序中

int main()
{
    sem_init();//创建或获取信号量id
    for(int i=0;i<5;i++)
    {
        sem_p();//p
        printf("A");
        fflush(stdout);

        int n = rand()%3;
        sleep(n);

        printf("A");
        fflush(stdout);
        sem_v();//v

        n = rand()%3;
        sleep(n);
    }

    sleep(10);
    sem_destroy();//销毁
}

a已经销毁了信号量,b最后就不用销毁了

最后同时运行ab程序,不会再出现两个程序同时使用资源的行为了,如下AA完一次再BB,然后AA……

ipcs可以查看消息队列、共享内存、信号量的使用情况,ipcrm可以对它们进行删除操作

-s信号量,-m共享内存,-q消息队列

销毁是把一组信号量都销毁


共享内存(shm)

a、b的逻辑空间中的某段物理内存用的同一块,这样a、b进程可以利用这块共享内存通信,不用像管道一样还需要拷贝。

shmget()创建或获取共享内存,返回共享内存的id,失败返回-1;

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

shmget(key,size,shmfkg);
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL

shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上

void* shmat(int shmid, const void *shmaddr, int shmflg);

shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写

shmdt()断开当前进程的 shmaddr 指向的共享内存映射

shmdt()成功返回 0, 失败返回-1
int shmdt(const void *shmaddr);

test.c输出共享内存中的东西

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
int main()
{
    //获取共享内存
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid==-1)
    {
        printf("shmid err");
        exit(1);
    }
    //链接
    char*p = (char*)shmat(shmid,NULL,0);
    if(p==(char*)-1)
    {
        printf("shmat err");
        exit(1);
    }
    //打印
    while(1)
    {
         if(strncmp(p,"end",3)==0)break;
         printf("%s\n",p);
         sleep(1);
    }
    //断开
    shmdt(p);
    //销毁共享内存
    if(shmctl(shmid,IPC_RMID,0)==-1)
        printf("shmctl err\n");
}

main.c,往共享内存中写入东西

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
int main()
{
    //创建共享内存
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid==-1)
    {
        printf("shmid err\n");
        exit(1);
    }
//把共享内存映射过来
    char *s = (char*)shmat(shmid,NULL,0);//s指向共享内存
    if(s == (char*)-1)
    {
       printf("shmat err");
        exit(1);
    }
    while(1)
    {
        printf("input:\n");
        char buff[128] = {0};
        fgets(buff,128,stdin);

        strcpy(s,buff);

        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
    }
//断开映射
    shmdt(s);
    exit(0);
}

为了让它写修改一次输出一次,可以使用信号量:

使用两个信号量s1==1,s2==0

更改后

main.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
#include"sem.h"
int main()
{
    //创建共享内存
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid==-1)
    {
        printf("shmid err\n");
        exit(1);
    }
//把共享内存映射过来
    char *s = (char*)shmat(shmid,NULL,0);//s指向共享内存
    if(s == (char*)-1)
    {
        printf("shmat err");
        exit(1);
    }
    sem_init();
    while(1)
    {
        printf("input:\n");
        char buff[128] = {0};
        fgets(buff,128,stdin);

        sem_p(SEM_W);
        strcpy(s,buff);
        sem_v(SEM_R);
        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
    }
//断开映射
    shmdt(s);
    exit(0);
}

test.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
#include"sem.h"
int main()
{
    //获取共享内存
    int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    if(shmid==-1)
    {
        printf("shmid err");
        exit(1);
    }
    //链接
    char*p = (char*)shmat(shmid,NULL,0);
    if(p==(char*)-1)
    {
        printf("shmat err");
        exit(1);
    }
    //打印
    sem_init();
    while(1)
    {
        sem_p(SEM_R);
        if(strncmp(p,"end",3)==0)break;
        printf("%s\n",p);
        sem_v(SEM_W);
    }
    //断开
    shmdt(p);
    //销毁共享内存
    if(shmctl(shmid,IPC_RMID,0)==-1)
        printf("shmctl err\n");
    sem_destroy();
}


消息队列

ipcrm -q 0删消息队列

-s信号量,-m共享内存

在内存中创建

msgsnd()发送

msgrcv()接收

msgctl()移除

msggst()创建

a.c程序

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
struct mess//自己定义消息结构体
{
       long type;
       char buff[32];
};
 
int main()
{
       int msgid = msgget((key_t)1234,IPC_CREAT|0600);
       if(msgid==-1)
       {
              printf(“msggeterr”);
              exit(1);
    }
    //写入数据,程序运行一次写入一次
    struct mess dt;
    dt.type = 1;//1号消息
    strcpy(dt.buff,”hello1”);
    //添加到消息队列
    msgsnd(msgid,&dt,32,0);
     
}

再运行一遍a.c又加入一个消息,已用字节数:32->64

b.c程序

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
struct mess
{
       long type;
       char buff[32];
};
int main()
{
       int msgid =msgget((key_t)1234,IPC_CREAT|0600);
       if(msgid ==-1)
    {
           printf(“msgget err\n”);
           exit(1);
    }
    struct mess dt;
    //读取消息
    msgrcv(msgid,&dt,32,1,0);//倒数第二个参数为指定读取几号类型消息,若为0,所有消息都可以读(不区分消息类型)。
    printf(“dt.buff=%s\n”,dt.buff);
}

读消息会取出消息队列中的消息

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

曦樂~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值