进程间通信:消息队列&信号量&共享内存

进程间通信之管道,点击这里:
http://blog.csdn.net/qq_33951180/article/details/68959819

消息队列

  • 消息队列是一种进程间发送二进制数据块的机制,每个数据块都有特定的类型,接受者进程可以有不同的类型值。
  • 使用消息队列,可以避免使用管道时带来的同步与阻塞等问题。但其也有缺点:与管道类似,在每一个数据块上都有一个最大尺寸限制。

现在我们实现一个消息队列,当然,在这之前先介绍几个函数:

  • 创建消息队列
int msgget(key_t key, int msgflg);

返回值:成功返回一个正整数(消息队列的标识符),失败返回-1并设置errno
参数:
key:是一个键值,用来标识一个全局唯一的消息队列。可以认为是一个端口号,也可以由ftok函数产生。
msgflg:
IPC_CREAT:如果IPC不存在,则创建一个IPC资源,否则打开操作;
IPC_EXCL:只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。
如果单独使用IPC_CREAT,***get()函数要么返回一个已经存在的共享内存的操作符,要么返回一个新建的共享内存的标识符。
如果将IPC_CREAT和IPC_EXCL一起使用,***get()将返回一个新建的IPC标识符;如果该IPC资源已经存在,或者返回-1。两个一起使用能够保证IPC资源是新建的,而不是打开已有对象。

【关于ftok函数】

key_t ftok(const char *pathname, int proj_id);

该函数把从pathname导出的信息与id的低序8为组合成一个IPC键(把一个已经存在的路径名转换成一个key_t值)。

  • 向消息队列读/写消息
读: ssize_t msgrcv(int msqid, void *msgp, size_t msgsz,
                     long msgtyp, int msgflg);
写: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

参数:
msqid:消息队列的标识码
msgp:指向消息缓冲区的指针,此位置用来暂时存储接收与发送的消息。被定义为如下类型:

struct msgbuf {
               long mtype;       /* 消息类型*/
               char mtext[1];    /* 消息数据 *
                };

msgsz:消息的大小。
msgtyp:从消息队列读取的消息形态。
msgtyp为0:读取消息队列中的第一个消息。

msgtyp为0:读取消息队列中的第一个消息。
>0:读取消息队列中第一个类型为msgtyp的消息(除非指定了标志MSG_EXCEPT)。
<0:读取消息队列中第一个类型值比msgtyp的绝对值小的消息。

msgflg:用来指明核心程序在消息队列没有消息的情况下应采取的行动。

msgflg和IPC_NOWAIT合用:则在msgsnd()执行时若是消息队列已满则msgsnd()将不会被阻塞,而是立刻返回-1。如果是msgrcv(),则在消息队列为空时会立即返回-1并设置错误码为ENOMSG
当msgflg为0时,msgsnd()和msgrcv()呈满或呈空时hi阻塞等待。
  • 属性控制
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

该函数是对msqid标识的消息队列执行cmd操作。
三种cmd:
IPC_STAT:该标识符用来获取消息队列对应的msqid_ds数据结构,并将其保存到buf指定的地址空间。
IPC_SET:设置消息队列的属性,要设置的属性存储在buf中。
IPC_RMID:从内核中删除msqid标识的消息队列。

消息队列实现代码:
https://github.com/lybb/Linux/tree/master/MsgQueue

信号量

  • 什么是信号量
    信号量的主要作用是为了保护临街资源,保证了在任意一个时刻内只有一个进程能进入临界区对资源进行操作。也就是说,信号量是用来协调进程对共享资源的访问的。

  • 工作原理
    信号量的本质是一种数据操作锁。其只能进行两种操作:等待和发送信号,即P、V操作。假设有信号量sv,
    P:如果信号量的值大于0,就将其减1;如果sv为0,就将进程挂起;—–释放资源
    V:如果有其他进程因为等待该信号量而挂起,则该操作是将其唤醒;如果没有,就将sv加1;—–申请资源

举个例子:假如有进程A、B共享信号量sv,当A进程执行了P操作后,就可以获得信号量sv并进入临界区,并将sv的值减1;当B进程来访问临界区时,其试图进行P操作,但是此时sv为0,则B进程就会被挂起等待直到A进程离开临界区并执行了V操作后,B进程被唤醒,然后就可以回复执行了。

  • 特点
    1、保护临界资源、协同进程;
    2、有公共、私有接口;
    3、生命周期随系统;
    4、创建、初始化不是原子操作,创建与初始化分开。

  • 系统调用
    主要有三个:semget,semop,semctl,都被设计为操作一组信号量,即信号量集。

【创建一个新的信号量集】

int semget(key_t key, int nsems, int semflg);

返回值:成功返回一个正整数,是信号量集的标识符;失败返回-1,并设置errno。
参数:
key:标识一个全局唯一的信号量集。要通过信号量进行通信的进程需要使用相同的键值来创建/获取信号量集。
nsems:指定要创建/获取的信号量集中信号量的数目。如果是创建,则改值必须被指定;若是获取,则可将其设置为0.
semflg:指定一组标志。低9位是信号量的权限。可以与IPC_CREATE按位“或”以创建新的信号量集。即使这个信号量集已经存在也不会产生错误。创建一组新的、唯一的信号量集—IPC_CREATE 和 IPC_EXCL,如果信号量集已经存在,则semget返回错误并设置errno为EEXIST。

【对信号量集进行操作】

int semop(int semid, struct sembuf *sops, unsigned nsops); 

返回值:成功返回0,失败返回-1,且sem_ops数组中指定的所有操作都不被执行。
参数:
semid:semget返回的信号量集标识符,用以指定被操作的目标信号量集。
sops:指向一个sembuf结构体。
sembuf结构体:unsigned short sem_num; 信号量集中信号量的编号
short sem_op; 指定操作类型(正数、0、负数)
short sem_flg; IPC_NOWAIT/IPC_UNDO

nsops:指定要执行的操作个数,即sops中元素的个数。semop对数组中的每个成员依次执行操作,且该过程是原子的。

【对信号量集进行控制】

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

返回值:成功时的参数取决于cmd参数;失败返回-1且设置errno
semid:由semget调用返回的信号量集标识符。
semnum:指定被操作的信号量在信号量集中的编号。(从0开始访问)
cmd:指定要执行的指令。(IPC_RMID:立即移除信号量集,唤醒所有等待该信号量集的所有进程)

信号量的实现代码:
https://github.com/lybb/Linux/tree/master/sem

共享内存

共享内存是进程间通信中最高效的一种方式,因为它不涉及进程之间的任何数据传输,但同样,这种高效仍然带来了问题,我们必须用其他手段来同步进程对共享内存的访问(因为共享内存是不带任何同步机制的),否则就会产生竞态条件。因此,共享内存通常是和其他进程间通信方式一起使用的。

  • 共享内存的特点
    1、使用灵活,可以是无关联的进程;
    2、效率高:程序直接访问内存,而不需要任何的书库拷贝。对于像管道和消息队列
    等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。

  • 创建共享内存

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

功能:创建一段新的共享内存,或者获取一段已经存在的共享内存。
返回值:成功时翻译一个正整数,即共享内存的标识符。失败时返回-1并设置errno。
参数:
key:是一个键值,用来标识一段全局唯一的共享内存。
size:指定共享内存的大小,单位是字节。如果是新创建的共享内存,则size值必须被指定。如果是获取已经存在的共享内存,则可以把size设置为0.
shmflg:该参数与semget系统调用的sem_flags参数相同。

  • 挂接 & 去挂接
    共享内存获取之后我们不能立即访问,而是先将它关联到进程的地址空间;使用完成后也需要将其从进程的地址空间上分离。
void *shmat(int shmid, const void *shmaddr, int shmflg); //挂接/关联
int shmdt(const void *shmaddr); //去挂接/去关联

shmid参数是由shmget调用返回的共享内存标识符。
shmaddr参数指定将共享内存关联到进程地址空间的哪块,最终效果还受到shmflg参数中可选标志的SHM_RND的影响。

shmaddr为NULL:被关联的地址由操作系统指定。(推荐这种做法,以确保代码的可移植性);
shmaddr为非NULL,且SHM_RND标志未被设置:共享内存被关联到指定的地址处。

shmat的返回值:成功时返回共享内存被关联的地址,失败返回-1并设置errno。
shmdt函数是将共享内存从进程地址空间中分离。成功时返回0,失败时返回-1并设置errno。

  • 控制共享内存的属性
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

cmd参数指定要执行的命令。shmctl所支持的cmd选项:

IPC_STAT    取该共享内存的shmid_ds结构,并存在第三个参数中
IPC_SET     使用buf指定的结构设置相关属性
IPC_RMID    删除指定共享内存,只有当buf中的shm_nattch值为0时才真正删除
IPC_LOCK    在内存中对共享内存加锁(超级用户权限)
IPC_UNLOCK  解锁共享内存(超级用户权限)

返回值:成功时的返回值取决与cmd参数。失败范湖-1并设置errno。

共享内存的代码实现:
https://github.com/lybb/Linux/tree/master/mem

你可能感兴趣的:(Linux,Linux,&,计算机网络)