栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

Linux进程间通信(管道,FIFO,消息队列,信号量,共享内存)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux进程间通信(管道,FIFO,消息队列,信号量,共享内存)

进程间通信:简称ipc(Inter-Process Communication)。进程间通信是指两个进程的数据之间产生交互。

目录

一.无名管道(pipe)

二.命名管道(FIFO)

三.消息队列

 四.信号量

 五.共享内存


一.无名管道(pipe)

1.特点:

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

②只能用于父子进程或兄弟进程之间

③可以把它当做是一个文件使用read等函数操作,但它不是普通文件,并不属于其他文件系统,只存于内存中。

2.原型:

1. #include 
2. Int pipe(int fd[2]); //返回值,若成功返回 0,失败返回-1
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而

3.例子:

#include 
#include 

int main()
{
        int pid;
        int fd[2];
        char buff[128];

        if(pipe(fd)==-1){                    //创建管道
                printf("pipe failedn");
        }

        if((pid=fork())==-1){                //创建子进程
                printf("fork failedn");
        }else if(pid==0){                       //child
                close(fd[1]);
                read(fd[0],buff,128);
                printf("child:message is %sn",buff);
        }else if(pid>0){                        //father
                close(fd[0]);
                write(fd[1],"hello world",128);
        }

        return 0;
}

二.命名管道(FIFO)

1.特点:

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

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

 2.原型

#include 
//返回值:成功返回 0,出错返回-1
Int mkfifo (const char *pathname, mode_t mode );

3例子:

读端:

include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
        int fd;
        char read_buff[128];

        if(mkfifo("./file",0600)==-1 && errno!=EEXIST){    //当fifo创建失败,并且不是因为文件已存在
                perror("mkfifo");
        }

        fd = open("./file",O_RDONLY);        //打开管道,以只读方式

        read(fd,read_buff,128);                //读数据,没有数据时阻塞

        printf("message is: %sn",read_buff);

        close(fd);

        return 0;
}

写端:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main()
{
        int fd;

        if(mkfifo("./file",0600)==-1 && errno!=EEXIST){
                perror("mkfifo");
        }

        fd = open("./file",O_WRONLY);

        write(fd,"hello world",128);

        close(fd);

        return 0;
}

三.消息队列

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

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

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

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

3.常用api

ftok()函数

系统IPC键值的格式转换函数,系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

key_t ftok( const char * fname, int id )
//系统IPC键值的格式转换函数

参数
fname:就是你指定的文件名(已经存在的文件名),一般使用当前目录

id:是子序号。虽然是int类型,但是只使用8bits(1-255)。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

msgget()

int msgget(key_t key, int flag);
// 创建或打开消息队列:成功返回队列ID,失败返回-1

参数:
key :消息队列的键值多个进程可以通过它访问同一个消息队列,如果key为IPC_PRIVATE 意思就是创建一个只能被创建进程读写的消息队列;如果不是IPC_PRIVATE .则我们可以指定一个值0x1234,还可以用ftok()函数来获得一个唯一的键值。

flag : 创建消息队列的创建方式或权限。IPC_CREAT :(需要指定权限:IPC_CREAT|0600)
如果消息队列对象不存在,则创建消息队列,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建,否则产生一个错误并返回。

msgsnd()

int msgsnd(int msqid, const void *ptr, size_t size, int flag);
//例
 msgend(msgId,&sndBuf,strlen(sndBuf.mtext),0);

参数:
msqid:消息队列的ID
ptr:发送给队列的消息。写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构:

struct msgbuf {
	long mtype;  //第一个字段必须是long类型
	char mtext[1]; 
};

size:ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说size是不包括长整型消息类型成员变量的长度。
flag: 为0表示在消息队列满或者空时都等待,直到队列中出现满足要求的消息为止(阻塞方式),设置IPC_NOWAIT 表示非阻塞方式

msgrcv()

int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
//例
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);

参数
msqid:消息队列的ID

ptr:写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:

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

type:消息类型
msgtyp=0 则返回队列的最早的一个消息。
msgtyp>0,则返回其类型为msgtyp的第一个消息。
msgtyp<0,则返回其类型小于或等于mtype(在msgbuf结构体中的mtype)参数的绝对值的最小的一个消息。

操作系统提供的消息队列,确保同一个类型的消息是先进先出的,整个队列的消息可能不是先进先出的。

size:数据的长度(同msgsnd)

flag:为0表示在消息队列满或者空时都等待,直到队列中出现满足要求的消息为止(阻塞方式),设置IPC_NOWAIT 表示非阻塞方式

msgctl()

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

参数:

msqid:消息队列的ID

cmd:函数要对消息队列进行的操作,IPC_RMID将队列从系统内核中删除(用来删除队列)

buf:队列内容(一般写NULL)

例子:

send.c

#include 
#include 
#include 
#include 
#include 
#include 

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

int main()
{
        key_t key;
        int msgId;
        struct msgbuf sendBuf; //定义一个用于发送消息的结构体

        key = ftok(".",1);     //使用ftok函数获得一个key值   

        if((msgId = msgget(key,IPC_CREAT|0600))==-1){    //创建消息队列
                perror("msgget");
        }

        sendBuf.mtype=8888;                    //给发消息的结构体添加内容
        strcpy(sendBuf.mtext,"hello world");
        if(msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0)){
                perror("magsnd");
        }

        return 0;
}

read.c

//除了一些内容其他同上个代码差不多

        if(msgrcv(msgId,&readBuf,128,8888,0)==-1){        //消息的接收
                perror("msgrcv");
        }

        printf("readbuf:%sn",readBuf.mtext);            //打印出拿到的消息

        if(msgctl(msgId,IPC_RMID,NULL)==-1){            //销毁消息队列
                perror("msgctl");
        }

 四.信号量

1.介绍:信号量主要用于实现进程间的互斥与同步。

临界资源:各进程采取互斥的方式,实现共享的资源称作临界资源。

2.特点:①信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

②信号量基于操作系统的 PV(p:拿钥匙,v:放钥匙) 操作,程序对信号量的操作都是原子操作。

③每次信号量的 PV 操作不仅限于对信号量值加 1 或减 1 ,而且可以加减任意正整数 ④支持信号量组 最简单常用的信号量是只能读取 0 和 1 的变量,叫二值信号量 3.常用api 创建或获取一个信号量组:
int semget(key_t key, int nsems, int semflg);
//若成功返回信号量集ID,失败返回-1

参数
key:所创建或打开信号量集的键值。
nsems:信号量的个数,通常为1;如果是引用一个现有的集合,则将nsems指定为 0,该参数只在创建信号量集时有效(也可以设置多个,比如设置成2,理解成一个管理a事件,另一个管理b事件)
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 | 表示。

对信号量组进行操作,改变信号量的值:

int semop(int semid,  struct sembuf *sops, unsigned nsops);  
//成功返回0,失败返回-1

参数
semid:semget()返回的信号量标识符。
*sops:结构的定义如下:

struct sembuf{
    short sem_num; // 除非使用一组信号量,否则它为0
    short sem_op;  // 信号量在一次操作中需要改变的数据,
    //通常是两个数,一个是-1,即P(拿钥匙)操作,一个是+1,即V(放回)操作。
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                   // 并在进程没有释放该信号量而终止时,操作系统释放信号量
};

nsops:信号操作结构的数量,恒大于或等于1。

控制信号量

int semctl(int semid, int sem_num, int cmd, ...);

参数:

semid:semget()返回的信号量标识符。
semnum:是操作信号在信号集中的编号,第一个信号的编号是0。
cmd:cmd通常是下面的两个值:


        SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。

        IPC_RMID:
用于删除一个已经无需继续使用的信号量标识符。

如果有第四个参数(需要用户自行定义),它通常是一个union semun结构,定义如下:

union semun {
	int val;    
	struct semid_ds *buf;    
	unsigned short  *array;  
	struct seminfo  *__buf;  
};

如何对多个信号量初始化:

semid =semget(key,3,IPC_CREAT|0666)
union semun arg;
unsigned short vals[3]={0};//假设初始化信号量集的值都为0
arg.array = vlas
semctl(sem_id, 0, SETALL, arg) //使用SETALL

例子:;

include 
#include 
#include 
#include 
#include 
#include 
union semun{
               int              val;    
               struct semid_ds *buf;    
               unsigned short  *array;  
               struct seminfo  *__buf;  
};

void p_getkey(int id)            //定义一个拿锁操作
{
        struct sembuf sops;

        sops.sem_num = 0;        
        sops.sem_op = -1;         // 信号量在一次操作中需要改变的数据
        sops.sem_flg = SEM_UNDO;  //SEM_UNDO阻塞方式

        if (semop(id, &sops, 1) == -1) {
               perror("semop");
        }
        printf("get the keyn");
}
void v_putkey(int id)            //放锁
{
        struct sembuf sops;

        sops.sem_num = 0;        
        sops.sem_op = 1;         
        sops.sem_flg = SEM_UNDO;

        if (semop(id, &sops, 1) == -1) {
               perror("semop");
        }
        printf("put the keyn");
}

int main()
{
        key_t key;
        int semId;
        union semun semun1;    

        key = ftok(".",2);

        semId = semget(key,1,IPC_CREAT|0666);

        semun1.val=1;
        semctl(semId,0,SETVAL,semun1);//"0"第0个信号量
        p_getkey(semId);        //调用拿锁函数
        //.................
        v_putkey(semId);        //调用放锁函数

        return 0;
}

 五.共享内存

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

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

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

2.如何查看/删除系统中的共享存储段:

查看:
ipcs -m
删除:
ipcrm -m shmid(通过ipcs -m查看得到)

3.常用api

获取创建:

int shmget(key_t key, size_t size, int flag);
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1

参数
key:由ftok生成的key标识

size:size以字节为单位指定需要共享的内存容量 ,在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。

0:只获取共享内存时指定为0

shmflg:如果要创建新的共享内存,需要使用IPC_CREAT(要附上权限类似open函数的mode参数),IPC_CREAT | IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
返回值
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因

映射:

// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);

参数
shm_id:是由shmget函数返回的共享内存标识。
*shm_addr:指定共享内存连接到当前进程中的地址位置,通常为0
shm_flg:若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段(为0)

返回值
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.

断开:

// 断开与共享内存的连接:成功返回0,失败返回-1
 int shmdt(void *addr); 

 断开。注意,并不是删除它,只是使该共享内存对当前进程不再可用

参数
*shmaddr:是shmat函数返回的地址指针。
返回值
调用成功时返回0,失败时返回-1.

// 控制共享内存的相关信息:成功返回0,失败返回-1
 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

参数
shm_id:shm_id是shmget函数返回的共享内存标识符。

command:command是要采取的操作,它可以取下面的三个值 :
IPC_STAT
IPC_SET
IPC_RMID 删除共享内存段

*buf:设置为0即可。

例子(与信号量配合):

write:

#include 
#include 
#include 
#include 
#include 
#include "config.h"
int main()
{
        key_t key;
        int shmId;
        int semId;
        char *sh_addr;

        key=ftok(".",2);
        if(key==-1){
                perror("ftok");
        }

        shmId=shmget(key,1024*2,IPC_CREAT|0600);
        if(shmId==-1){
                perror("shmget");
        }

        semId=get_semId(key);        //自己写的函数用于获取信号量的id

        sh_addr=shmat(shmId,0,0);
        printf("shmat successn");
        int i=0;
        while(1){
                sleep(8);
                p_getkey(semId);     //拿锁
                strcpy(sh_addr,"liuzheyu shuain");    //用strcpy,用其他会出问题
                v_putkey(semId);     //放锁
        }
        shmdt(sh_addr);              //断开共享;内存的链接

        return 0;
}

read:

#include 
#include 
#include 
#include 
#include 
#include "config.h"
int main()
{
        key_t key;
        int shmId;
        int semId;
        char *sh_addr;

        key=ftok(".",2);
        if(key==-1){
                perror("ftok");
        }

        shmId=shmget(key,1024*2,IPC_CREAT|0600);
        if(shmId==-1){
                perror("shmget");
        }

        semId=get_semId(key);

        sh_addr=shmat(shmId,0,0);
        printf("shmat successn");
        while(1){
                sleep(5);
                p_getkey(semId);
                printf(":%sn",sh_addr);
                v_putkey(semId);
        }
        shmdt(sh_addr);

        shmctl(shmId,IPC_RMID,0);

        return 0;
}

config.h(信号量部分):

#include 
#include 
#include 
#include 
#include 
#include 
union semun{
               int              val;    
               struct semid_ds *buf;    
               unsigned short  *array;  
               struct seminfo  *__buf;  
};

union semun semun1;

int get_semId(int key){
        int semId = semget(key,1,IPC_CREAT|0666);
        if(semId==-1){
                perror("semId");
        }

        semun1.val=1;
        semctl(semId,0,SETVAL,semun1);

        return semId;
}
void p_getkey(int id)
{
        struct sembuf sops;

        sops.sem_num = 0;        
        sops.sem_op = -1;         
        sops.sem_flg = SEM_UNDO;

        if (semop(id, &sops, 1) == -1) {
               perror("semop");
        }
        printf("get the keyn");
}
void v_putkey(int id)
{
        struct sembuf sops;
        sops.sem_num = 0;        
        sops.sem_op = 1;         
        sops.sem_flg = SEM_UNDO;

        if (semop(id, &sops, 1) == -1) {
               perror("semop");
        }
        printf("put the keyn");
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/954482.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号