Linux进程间通信

目录

进程间通信(IPC机制)

一、IPC机制有哪些方式

二、管道

2.1作用和特点:

2.2管道的分类

2.3无名管道

2.4有名管道

2.5使用无名管道实现父子进程间通讯

2.6使用有名管道实现进程间通讯

2.7文件描述符的复制,重定向

三、信号量

3.1信号量的值与操作

3.2信号量API

3.3信号量的函数实现

3.4信号量的使用

3.5 ipcs 和 ipcrm命令

四、共享内存

4.1共享内存的原理

4.2两个进程间使用共享内存

4.3 ipcs和ipcrm命令

五、消息队列

5.1消息队列的实现


进程间通信(IPC机制)

一、IPC机制有哪些方式

总共有五个管道、信号量、共享内存、消息队列、套接字

三个空填(信号量、共享内存、消息队列)

二、管道

2.1作用和特点:

用来在两个进程之间传递数据

只能以只读或者只写(半双工)

无论有名还是无名,写入管道的数据都存储在内存中

管道文件创建在内存中

2.2管道的分类

无名管道 有名管道

2.3无名管道

主要用于父子进程间的通信

创建无名管道命令: int fd[2]

fd为文件描述符,使用pipe(fd)的返回值判断是否成功,成功返回0,失败返回-1;对于该无名管道,fd[0]是管道读端的描述符,fd[1]是管道写端的描述符。

2.4有名管道

用于任意进程间的通信

创建有名管道命令: mkfifo FIFO

2.5使用无名管道实现父子进程间通讯

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
    int fd[2];
    if(pipe(fd)==-1)
    {
        exit(-1);
    }
    pid_t pid = fork();
    if(pid==-1)
    {
        exit(-1);
    }
    if(pid==0)//子进程读取数据   关闭写端
    {
        close(fd[1]);//在进行进程复制后,父子进程都有一个管道的读写两端fd[0]和fd[1],管道是半双工在同一时间只能读或者写。子进程进行读操作时,所以需要关闭该管道的写端
        char buff[128]={0};
        read(fd[0],buff,127);
        printf("%s\n",buff);
        close(fd[0]);
    }
    else//父进程写入数据   关闭读端
    {
        close(fd[0]);
        write(fd[1],"hello",5);
        close(fd[1]);
    }
    exit(0);
}

2.6使用有名管道实现进程间通讯

第一步:创建有名管道文件

 第二步:创建a.c和b.c分别实现向FIFO里面写入数据和从中读取数据

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
//a.c   写入数据
int main()
{
    int fdw=open("FIFO",O_WRONLY);
    if(fdw==-1)
    {
		exit(-1);
    }
    char buff[128]={0};
    while(1)
    {
        memset(buff,0,128);
        printf("input:");
        fgets(buff,128,stdin);
        if(strcmp(buff,"end")==0)
        {
            break;
        }
        write(fdw,buff,strlen(buff));
    }
    close(fdw);
    exit(0);
}
//b.c  读取数据
int main()
{
    int fdw=open("FIFO",O_ONONLY);
    if(fdw==-1)
    {
		exit(-1);
    }
    char buff[128]={0};
    while(1)
    {
        memset(buff,0,128);
        int num=read(fdw,buff,128);
        if(num==0)
        {
            break;
        }
        printf("read=%s",buff);
    }
    close(fdw);
    exit(0);
}

第三步:实现进程间通讯

 

2.7文件描述符的复制,重定向

在shell中运行一个进程,默认会有三个文件描述符,分别是:

0 -> stdin(标准输入)

1 -> stdout(标准输出)

2 -> stderr(标准错误)

函数dup(int fd)

作用:将fd复制一份,分配给文件表中未被使用的最小的一项,其返回值为分配后的文件描述符。

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
    int fd = open("a.txt", O_RDONLY);
    int copyfd = dup(fd);
    //将fd阅读文件置于文件末尾,计算偏移量。
    printf("fd = %d偏移量:%d",fd,lseek(fd, 0, SEEK_END));
    //现在我们计算copyFd的偏移量
    printf("copyfd = %d偏移量:%d",copyfd,lseek(cpoyfd, 0, SEEK_END));
    exit(0);
}

输出结果为:fd = 3 偏移量: 75 copyFd = 4偏移量:75

其实就是给fd所指向的文件再分配一个最小的未被使用的文件描述符(返回值),也就是fd 和 copyfd均指向同一个文件,且共享偏移量和文件状态

使用的函数dup2 ( int oldfd , int newfd)

在打开文件后,系统给文件的描述符会从3开始依次往下分配。dup2函数会先判断新的文件描述符(newfd)旧的文件描述符(oldfd)是否是同一个值,如果是就直接返回newfd。如果不是,会先把newfd指向的文件关闭,然后将oldfd 复制给新newfd (其实就是让新的文件描述符都指向旧的文件描述符所指向的文件)。

例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
	int oldfd=open("a.txt",O_CREAT|O_WRONLU,0600);
    dup2(oldfd,1);
    printf("hello\n");
	exit(0);
}

运行后,会发现 hello 会打印到 a.txt 文件中不会显示在屏幕上。因为printf 函数是会将内容输出到 1 对应的文件中, 1 原本对应着 stdout 标准输出文件 ,在执行dup2函数后,1 此时对应着 a.txt 文件,所以printf函数会将 hello 打印到a.txt文件中。

三、信号量

作用:当多个进程访问同一个临界资源时会发生冲突,信号量用来控制进程访问临界资源,用于进程间同步

进程间同步:同一时刻,只能有一个进程访问资源。若资源被占用,程序就会被阻塞,实现对程序的控制。

3.1信号量的值与操作

信号量为特殊的变量,其值是大于等于0的,值的大小为可用资源的数目。为0时,说明没有可用资源。

当信号量的值只有(0,1)时,就叫做2值信号量

存在两种操作:

p操作:获取资源 -1 当信号量的值为0时p操作阻塞

v操作:释放资源 +1

临界资源:同一时刻只允许一个进程访问的资源。

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

信号量分为内核态信号量和用户态信号量,以下介绍的都是内核态信号量。

3.2信号量API

使用的函数:

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

semop() P,V操作 IPC_UNDO : 如果P()操作后,程序异常终止,未释放资源,系统会去释放这个资源。IPC_NOWAIT

semctl() 对信号量进行初始化和删除 参数分别为 SETVAL(初始化),IPC_RMID(删除)

3.3信号量的函数实现

//sem.h
#include<stdio.h>
#include<stdlib.h>
#include<unistdh>
#include<sys/sem.h>

union semun
{
    int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();

sem.c文件

信号量的初始化函数:sem_init()

使用到的函数有:semget()+semctl()

#include"sem.h"
static int semid;//静态全局变量,只限本文件
void sem_init()
{
   semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//创建信号量。key值为1234,表示使用该函数的进程用的是同一个信号量。1表示一个信号量
    if(semid==-1)//创建失败(已存在信号量,不要创建信号量,直接获取)
    {
        semid = semget((key_t)1234,1,0600);
        if(semid==-1)
        {
            exit(1);
        }
    }
    else//创建成功
    {
        union semun a;
        a.val=1;
        if(semctl(semid,0,SETVAL,a)==-1)//初始化信号量。0表示为0号下标,一个信号量就是0下标
        {
            perror("semctl err");
            exit(1);
        }
    }
}

信号量的P函数:sem_p()

使用到的函数有:semop()

#incldue"sem.h"
void sem_p()
{
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=-1;//获取资源
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("semop err");
        exit(1);
    }
}

信号量的V函数:sem_v()

使用到的函数有:semop()

#incldue"sem.h"
void sem_v()
{
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=1;//释放资源
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("semop err");
        exit(1);
    }
}

信号量的批量删除函数:sem_destroy()

使用到的函数有:semctl()

#incldue"sem.h"
void sem_destroy()
{
    if(semctl(semid,0,IPC_RMID)==-1)
    {
        perror("semctl error");
        exit(1);
    }
}

3.4信号量的使用

//a.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"sem.h"
int main()
{
    sem_init();//创建或者获取信号量
    for(int i=0;i<5;++i)
    {
        sem_p();//代码1
        printf("A");
        fflush(stdout);
        sleep(1);
        printf("A");
        fflush(stdout);
        sem_v();//代码2
        sleep(1);
    }
    //a.c程序睡眠10s,默认两个程序都使用完资源,需要销毁信号量。销毁信号量只能在一个程序中进行销毁,不能两个程序都进行销毁。
    sleep(10);
    sem_destroy();
    exit(0);
}
//b.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include"sem.h"
int main()
{
    sem_init();//创建或者获取信号量
    for(int i=0;i<5;++i)
    {
        sem_p();//代码3
        printf("B");
        fflush(stdout);
        sleep(1);
        printf("B");
        fflush(stdout);
        sem_v();//代码4
        sleep(1);
    }
    exit(0);
}

在未加代码1,2,3,4时,同时运行a.c和b.c程序后,运行结果为ABABABABABABABABABAB,存在不成对,说明A在打印时B也进行打印;在加入这四行代码后,运行结果为AABBAABBAABBAABBAABB,均成对出现。

3.5 ipcs 和 ipcrm命令

ipcs: 查看信号量,共享内存,消息队列

ipcs -s:查看信号量

ipcrm -s + semid(要删除信号量的id): 删除该id的信号量

四、共享内存

4.1共享内存的原理

共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

多个进程在物理内存上有内存空间是共享的,多个进程在各自的逻辑地址空间写入数据,或读取数据,使用同一个空间,不需要数据的拷贝。

4.2两个进程间使用共享内存

步骤一:a进程创建共享内存并实现地址映射,b进程获取共享内存空间并实现地址映射。(或者b创建,a获取)

步骤二:a进程向共享内存中写入数据,b进程从共享内存中读取数据。

步骤三:a和b进程删除映射关系。

需要通过信号量实现a和b进程的同步控制

a写一次,b读一次,a不写,b不读,a不能连续写,b不能连续读

若使用一个信号量,因为a和b进程不确定哪个先运行,会导致无法满足上述条件。要完成上述情况,需要设置两个信号量s1(初值设置为1)和s2(初值设置为0)。要使a进程先运行,则需要对a进程先p(s1),执行后再v(s2);对b进程先p(s2),执行后再v(s1)。

如a进程不执行则无法v(s2),s2的值会一直为0,b进程将执行p(s2)操作会阻塞住,以至于a进程一定会先于b进程执行。b进程不执行无法v(s1),则s1的值会一直为0,a进程执行p(s1)操作会阻塞住,以至于b进程不执行完毕,a进程无法继续执行,便可以实现上述要求。

使用的函数:

shmget 创建、获取

shmat 映射

shmdt 断开映射

shmctl 删除共享内存

//sem.h
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/sem.h>
#define SEM1 0
#define SEM2 1
#define SEM_MAX 2
//信号量的个数
union semun
{
    int val;
}
void sem_init();
void sem_p(int index);
void sem_v(int index);
void sem_destroy();
#include"sem.h"
//sem.c
static int semid;//静态全局变量,只限本文件
void sem_init()
{
   semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);
    if(semid==-1)
    {
        semid = semget((key_t)1234,SEM_MAX,0600);
        if(semid==-1)
        {
            exit(1);
        }
    }
    else
    {
        union semun a;
        int arr[2]={1,0};
        for(int i = 0;i<SEM_MAX;++i)
        {
            a.val=arr[i];
        	if(semctl(semid,i,SETVAL,a) == -1)
        	{
            	perror("semctl err");
           	 	exit(1);
        	}
        }   
    }
}
void sem_p(int index)
{
    if(index<0||index>=SEM_MAX)
    {
        exit(1);
    }
    struct sembuf buf;
    buf.sem_num = index;
    buf.sem_op = -1;//获取资源
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("semop err");
        exit(1);
    }
}
void sem_v(int index)
{
    struct sembuf buf;
    buf.sem_num = index;
    buf.sem_op = 1;//释放资源
    buf.sem_flg=SEM_UNDO;
    if(semop(semid,&buf,1)==-1)
    {
        perror("semop err");
        exit(1);
    }
}
void sem_destroy()
{
    if(semctl(semid,0,IPC_RMID)==-1)
    {
        perror("semctl error");
        exit(1);
    }
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include"sem.h"
//a.c  向共享内存中写入数据
int main()
{
    int shmid=shmget((key_t)1234,128,IPC_CREAT|0600));
    if(-1==shmid)
    {
        exit(1);
    }
    //创建完成后映射物理内存 
    char* s = (char*)shmat(shmid,NULL,0);//共享内存的地址是内核帮忙分配的,返回共享内存的地址。0代表读写权限都有
    if(s == (char*)-1)
    {
        exit(1);
    }
    //向s指向的共享内存空间写入数据
    sem_init();
    while(1)
    {
        printf("input:");
        fgets(s,128,stdin);
        sem_p(SEM1);
        if(strncmp(s,"end",3) == 0)
        {
            sem_v(SEM2);//当输入end后,需要给SEM2 V操作,否则b进程无法p(SEM2)操作,将无法读取到end。将会p操作异常
            break;
        }
        sem_v(SEM2);
    }
    sem_destroy();
    shmdt(s);//断开映射
    shmctl(shmid,IPC_RMID,NULL);//删除共享内存
    exit(0);
}
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include<sem.h>
//b.c  从共享内存中读取数据
int main()
{
    int shmid=shmget((key_t)1234,128,IPC_CREAT|0600));
    if(-1==shmid)
    {
        exit(1);
    }
    //获取完成后映射物理内存 
    char* s = (char*)shmat(shmid,NULL,0);//共享内存的地址是内核帮忙分配的,返回共享内存的地址。0代表读写权限都有
    if(s == (char*)-1)
    {
        exit(1);
    }
    //从s指向的共享内存空间读取数据
    sem_init();
    while(1)
    {
        sem_p(SEM2);
        if(stcncmp(s,"end",3) == 0)
        {
            break;
        }
        sem_v(SEM1);
        printf("read:%s\n",s);
        sleep(1);
    }
    shmdt(s);
    exit(0);
}

4.3 ipcs和ipcrm命令

ipcs -m:查看信号量

ipcrm -m + shmid(要删除共享内存的id): 删除该id的共享内存

五、消息队列

自身存在同步机制,实现时不需要使用信号量

5.1消息队列的实现

使用到的函数:

msgget() 创建或者获取一个消息队列

msgsnd() 发送一条消息

msgrcv() 接收一条消息

msgctl() 控制消息队列

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
typedef struct mess
{
    long mtype;
    char mtext[128];
}msgdata;
//a.c
int main()
{
    //获取或创建消息队列
    int msgid = msgget((key_t)1234,IPC_CREAT|0664);
    if(msgid == -1)
    {
        exit(1);
    }
    //向消息队列发送消息,从键盘中获取
    msgdata data;
    data.mtype = 1;
    strcpy(data.mtext,"hello1");
    msgsnd(msgid,&data,128,0);
    exit(0);
}
//b.c
int main()
{
    //获取消息队列
    int msgid = msgget((key_t)1234,IPC_CREAT|0664);
    if(msgid == -1)
    {
        exit(1);
    }
    //从消息队列获取消息
    msgdata data;
    msgrcv(msgid,&data,128,1,0);//第三个参数1的意义是读取1号消息,如果把1改成0则表示读取任意消息。
    //第四个参数0的意义在于,如果b进程获取消息,不存在1消息,则会阻塞。
    printf("data type:%ld\ndata text:%s\n",data.mtype,data.mtext);
    msgctl(msgid,IPC_RMID,0);
    exit(0);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值