进程间通信方式

进程间通信方式,第1张

在操作系统中,一个进程可以理解为是关于计算机资源集合的一次运行活动,其就是一个正在执行的程序的实例。从概念上来说,一个进程拥有它自己的虚拟CPU和虚拟地址空间,任何一个进程对于彼此而言都是相互独立的,这也引入了一个问题 —— 如何让进程之间互相通信?

由于进程之间是互相独立的,没有任何手段直接通信,因此我们需要借助操作系统来辅助它们。举个通俗的例子,假如A与B之间是独立的,不能彼此联系,如果它们想要通信的话可以借助第三方C,比如A将信息交给C,C再将信息转交给B —— 这就是进程间通信的主要思想 —— 共享资源。

这里要解决的一个重要的问题就是如何避免竞争,即避免多个进程同时访问临界区的资源。

共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。

你可能会想到,我直接创建一个文件,然后进程不就都可以访问了?

是的,但这个方法有几个缺陷:

Linux下采用共享内存的方式来使进程完成对共享资源的访问,它将磁盘文件复制到内存,并创建虚拟地址到该内存的映射,就好像该资源本来就在进程空间之中,此后我们就可以像操作本地变量一样去操作它们了,实际的写入磁盘将由系统选择最佳方式完成,例如操作系统可能会批量处理加排序,从而大大提高IO速度。

如同上图一样,进程将共享内存映射到自己的虚拟地址空间中,进程访问共享进程就好像在访问自己的虚拟内存一样,速度是非常快的。

共享内存的模型应该是比较好理解的:在物理内存中创建一个共享资源文件,进程将该共享内存绑定到自己的虚拟内存之中。

这里要解决的一个问题是如何将同一块共享内存绑定到自己的虚拟内存中,要知道在不同进程中使用 malloc 函数是会顺序分配空闲内存,而不会分配同一块内存,那么要如何去解决这个问题呢?

Linux操作系统已经想办法帮我们解决了这个问题,在 #include <sys/ipc.h>和 #include <sys/shm.h>头文件下,有如下几个shm系列函数:

通过上述几个函数,每个独立的进程只要有统一的共享内存标识符便可以建立起虚拟地址到物理地址的映射,每个虚拟地址将被翻译成指向共享区域的物理地址,这样就实现了对共享内存的访问。

还有一种相像的实现是采用mmap函数,mmap通常是直接对磁盘的映射——因此不算是共享内存,存储量非常大,但访问慢; shmat与此相反,通常将资源保存在内存中创建映射,访问快,但存储量较小。

不过要注意一点,操作系统并不保证任何并发问题,例如两个进程同时更改同一块内存区域,正如你和你的朋友在线编辑同一个文档中的同一个标题,这会导致一些不好的结果,所以我们需要借助信号量或其他方式来完成同步。

信号量是迪杰斯特拉最先提出的一种为解决 同步不同执行线程问题 的一种方法,进程与线程抽象来看大同小异,所以 信号量同样可以用于同步进程间通信

信号量 s 是具有非负整数值的全局变量,由两种特殊的 原子操作 来实现,这两种原子操作称为 P 和 V :

信号量并不用来传送资源,而是用来保护共享资源,理解这一点是很重要的,信号量 s 的表示的含义为 同时允许最大访问资源的进程数量 ,它是一个全局变量。来考虑一个上面简单的例子:两个进程同时修改而造成错误,我们不考虑读者而仅仅考虑写者进程,在这个例子中共享资源最多允许一个进程修改资源,因此我们初始化 s 为1。

开始时,A率先写入资源,此时A调用P(s),将 s 减一,此时 s = 0,A进入共享区工作。

此时,进程B也想进入共享区修改资源,它调用P(s)发现此时s为0,于是挂起进程,加入等待队列。

A工作完毕,调用V(s),它发现s为0并检测到等待队列不为空,于是它随机唤醒一个等待进程,并将s加1,这里唤醒了B。

B被唤醒,继续执行P操作,此时s不为0,B成功执行将s置为0并进入工作区。

此时C想要进入工作区......

可以发现,在无论何时只有一个进程能够访问共享资源,这就是信号量做的事情,他控制进入共享区的最大进程数量,这取决于初始化s的值。此后,在进入共享区之前调用P操作,出共享区后调用V操作,这就是信号量的思想。

在Linux下并没有直接的P&V函数,而是需要我们根据这几个基本的sem函数族进行封装:

正如其名,管道就如同生活中的一根管道,一端输送,而另一端接收,双方不需要知道对方,只需要知道管道就好了。

管道是一种最 基本的进程间通信机制。 管道由pipe函数来创建: 调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。管道被分为匿名管道和有名管道。

匿名管道通过pipe函数创建,这个函数接收一个长度为2的Int数组,并返回1或0表示成功或者失败:

int pipe(int fd[2])

这个函数打开两个文件描述符,一个读端文件,一个写端,分别存入fd[0]和fd[1]中,然后可以作为参数调用 write 和 read 函数进行写入或读取,注意fd[0]只能读取文件,而fd[1]只能用于写入文件。

你可能有个疑问,这要怎么实现通信?其他进程又不知道这个管道,因为进程是独立的,其他进程看不到某一个进程进行了什么操作。

是的,‘其他’进程确实是不知道,但是它的子进程却可以!这里涉及到fork派生进程的相关知识,一个进程派生一个子进程,那么子进程将会复制父进程的内存空间信息,注意这里是复制而不是共享,这意味着父子进程仍然是独立的,但是在这一时刻,它们所有的信息又是相等的。因此子进程也知道该全局管道,并且也拥有两个文件描述符与管道挂钩,所以 匿名管道只能在具有亲缘关系的进程间通信。

还要注意,匿名管道内部采用环形队列实现,只能由写端到读端,由于设计技术问题,管道被设计为半双工的,一方要写入则必须关闭读描述符,一方要读出则必须关闭写入描述符。因此我们说 管道的消息只能单向传递。

注意管道是堵塞的,如何堵塞将依赖于读写进程是否关闭文件描述符。如果读管道,如果读到空时,假设此时写端口还没有被完全关闭,那么操作系统会假设还有数据要读,此时读进程将会被堵塞,直到有新数据或写端口被关闭;如果管道为空,且写端口也被关闭,此时操作系统会认为已经没有东西可读,会直接退出,并关闭管道。

对于写一个已经满了的管道同理而言。

管道内部由内核管理,在半双工的条件下,保证数据不会出现并发问题。

了解了匿名管道之后,有名管道便很好理解了。在匿名管道的介绍中,我们说其他进程不知道管道和文件描述符的存在,所以匿名管道只适用于具有亲缘关系的进程,而命名管道则很好的解决了这个问题 —— 现在管道有一个唯一的名称了,任何进程都可以访问这个管道。

注意,操作系统将管道看作一个抽象的文件,但管道并不是普通的文件,管道存在于内核空间中而不放置在磁盘(有名管道文件系统上有一个标识符,没有数据块),访问速度更快,但存储量较小,管道是临时的,是随进程的,当进程销毁,所有端口自动关闭,此时管道也是不存在的,操作系统将所有IO抽象的看作文件,例如网络也是一种文件,这意味着我们可以采用任何文件方法操作管道,理解这种抽象是很重要的,命名管道就利用了这种抽象。

Linux下,采用mkfifo函数创建,可以传入要指定的‘文件名’,然后其他进程就可以调用open方法打开这个特殊的文件,并进行write和read操作(那肯定是字节流对吧)。

注意,命名管道适用于任何进程,除了这一点不同外,其余大多数都与匿名管道相同。

消息队列亦称报文队列,也叫做信箱,是Linux的一种通信机制,这种通信机制传递的数据会被拆分为一个一个独立的数据块,也叫做消息体,消息体中可以定义类型与数据,克服了无格式承载字节流的缺陷(现在收到void*后可以知道其原本的格式惹):

同管道类似,它有一个不足就是每个消息的最大长度是有上限的,整个消息队列也是长度限制的。

内核为每个IPC对象维护了一个数据结构struct ipc_perm,该数据结构中有指向链表头与链表尾部的指针,保证每一次插入取出都是O(1)的时间复杂度。

一个进程可以发送信号给另一个进程,一个信号就是一条消息,可以用于通知一个进程组发送了某种类型的事件,该进程组中的进程可以采取处理程序处理事件。

Linux下 unistd.h 头文件下定义了如图中的常量,当你在shell命令行键入 ctrl + c 时,内核就会前台进程组的每一个进程发送 SIGINT 信号,中止进程。

我们可以看到上述只有30个信号,因此操作系统会为每一个进程维护一个int类型变量sig,利用其中30位代表是否有对应信号事件,每一个进程还有一个int类型变量block,与sig对应,其30位表示是否堵塞对应信号(不调用处理程序)。如果存在多个相同的信号同时到来,多余信号会被存储在一个等待队列中等待。

我们要理解进程组是什么,每个进程属于一个进程组,可以有多个进程属于同一个组。每个进程拥有一个进程ID,称为 pid ,而每个进程组拥有一个进程组ID,称为 pgid ,默认情况下,一个进程与其子进程属于同一进程组。

软件方面(诸如检测键盘输入是硬件方面)可以利用kill函数发送信号,kill函数接受两个参数,进程ID和信号类型,它将该信号类型发送到对应进程,如果该pid为0,那么会发送到属于自身进程组的所有进程。

接收方可以采用signal函数给对应事件添加处理程序,一旦事件发生,如果未被堵塞,则调用该处理程序。

Linux下有一套完善的函数用以处理信号机制。

Socket套接字是用与网络中不同主机的通信方式,多用于客户端与服务器之间,在Linux下也有一系列C语言函数,诸如socket、connect、bind、listen与accept,我们无需花太多时间研究这些函数,因为我们可能一辈子都不会与他们打交道,对于原理的学习,后续我会对Java中的套接字socket源码进行剖析。

对于工作而言,我们可能一辈子都用不上这些操作,但作为对于操作系统的学习,认识到进程间是如何通信还是很有必要的。

面试的时候对于这些方法我们不需要掌握到很深的程度,但我们必须要讲的来有什么通信方式,这些方式都有什么特点,适用于什么条件,大致是如何操作的,能说出这些,基本足以让面试官对你十分满意了。

进程间的通信方式:

1.管道(pipe)及有名管道(named pipe):

管道可用于具有亲缘关系进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。

2.信号(signal):

信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致得。

3.消息队列(message queue):

消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。

消息缓冲通信技术是由Hansen首先提出的,其基本思想是:根据”生产者-消费者”原理,利用内存中公用消息缓冲区实现进程之间的信息交换.

内存中开辟了若干消息缓冲区,用以存放消息.每当一个进程向另一个进程发送消息时,便申请一个消息缓冲区,并把已准备好的消息送到缓冲区,然后把该消息缓冲区插入到接收进程的消息队列中,最后通知接收进程.接收进程收到发送里程发来的通知后,从本进程的消息队列中摘下一消息缓冲区,取出所需的信息,然后把消息缓冲区不定期给系统.系统负责管理公用消息缓冲区以及消息的传递.

一个进程可以给若干个进程发送消息,反之,一个进程可以接收不同进程发来的消息.显然,进程中关于消息队列的操作是临界区.当发送进程正往接收进程的消息队列中添加一条消息时,接收进程不能同时从该消息队列中到出消息:反之也一样.

消息缓冲区通信机制包含以下列内容:

(1) 消息缓冲区,这是一个由以下几项组成的数据结构:

1、 消息长度

2、 消息正文

3、 发送者

4、 消息队列指针

(2)消息队列首指针m-q,一般保存在PCB中。

(1) 互斥信号量m-mutex,初值为1,用于互斥访问消息队列,在PCB中设置。

(2) 同步信号量m-syn,初值为0,用于消息计数,在PCB中设置。

(3) 发送消息原语send

(4) 接收消息原语receive(a)

4.共享内存(shared memory):

可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。

这种通信模式需要解决两个问题:第一个问题是怎样提供共享内存;第二个是公共内存的互斥关系则是程序开发人员的责任。

5.信号量(semaphore):

主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。

6.套接字(socket);

这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛。

http://blog.csdn.net/eroswang/archive/2007/09/04/1772350.aspx

linux下的进程间通信-详解

详细的讲述进程间通信在这里绝对是不可能的事情,而且笔者很难有信心说自己对这一部分内容的认识达到了什么样的地步,所以在这一节的开头首先向大家推荐著 名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX Environment》,它的中文译本《UNIX环境高级编程》已有机械工业出版社出版,原文精彩,译文同样地道,如果你的确对在Linux下编程有浓 厚的兴趣,那么赶紧将这本书摆到你的书桌上或计算机旁边来。说这么多实在是难抑心中的景仰之情,言归正传,在这一节里,我们将介绍进程间通信最最初步和最 最简单的一些知识和概念。

首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法。但一般说来, 进程间通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法。Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式)。而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。

2.3.1 管道

管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。

无名管道由pipe()函数创建:

#include <unistd.h>

int pipe(int filedis[2]);

参数filedis返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。

#define INPUT 0

#define OUTPUT 1

void main() {

int file_descriptors[2]

/*定义子进程号 */

pid_t pid

char buf[256]

int returned_count

/*创建无名管道*/

pipe(file_descriptors)

/*创建子进程*/

if((pid = fork()) == -1) {

printf("Error in fork\n")

exit(1)

}

/*执行子进程*/

if(pid == 0) {

printf("in the spawned (child) process...\n")

/*子进程向父进程写数据,关闭管道的读端*/

close(file_descriptors[INPUT])

write(file_descriptors[OUTPUT], "test data", strlen("test data"))

exit(0)

} else {

/*执行父进程*/

printf("in the spawning (parent) process...\n")

/*父进程从管道读取子进程写的数据,关闭管道的写端*/

close(file_descriptors[OUTPUT])

returned_count = read(file_descriptors[INPUT], buf, sizeof(buf))

printf("%d bytes of data received from spawned process: %s\n",

returned_count, buf)

}

}

在Linux系统下,有名管道可由两种方式创建:命令行方式mknod系统调用和函数mkfifo。下面的两种途径都在当前目录下生成了一个名为myfifo的有名管道:

方式一:mkfifo("myfifo","rw")

方式二:mknod myfifo p

生成了有名管道后,就可以使用一般的文件I/O函数如open、close、read、write等来对它进行操作。下面即是一个简单的例子,假设我们已经创建了一个名为myfifo的有名管道。

/* 进程一:读有名管道*/

#include <stdio.h>

#include <unistd.h>

void main() {

FILE * in_file

int count = 1

char buf[80]

in_file = fopen("mypipe", "r")

if (in_file == NULL) {

printf("Error in fdopen.\n")

exit(1)

}

while ((count = fread(buf, 1, 80, in_file)) >0)

printf("received from pipe: %s\n", buf)

fclose(in_file)

}

/* 进程二:写有名管道*/

#include <stdio.h>

#include <unistd.h>

void main() {

FILE * out_file

int count = 1

char buf[80]

out_file = fopen("mypipe", "w")

if (out_file == NULL) {

printf("Error opening pipe.")

exit(1)

}

sprintf(buf,"this is test data for the named pipe example\n")

fwrite(buf, 1, 80, out_file)

fclose(out_file)

}

2.3.2 消息队列

消息队列用于运行于同一台机器上的进程间通信,它和管道很相似,是一个在系统内核中用来保存消息的队列,它在系统内核中是以消息链表的形式出现。消息链表中节点的结构用msg声明。

事实上,它是一种正逐渐被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它,所以,我们对此方式也不再解释,也建议读者忽略这种方式。

2.3.3 共享内存

共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的将是 实际的物理内存,在Linux系统下,这只有通过限制Linux系统存取的内存才可以做到,这当然不太实际。常用的方式是通过shmXXX函数族来实现利 用共享内存进行存储的。

首先要用的函数是shmget,它获得一个共享存储标识符。

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

int shmget(key_t key, int size, int flag)

这个函数有点类似大家熟悉的malloc函数,系统按照请求分配size大小的内存用作共享内存。Linux系统内核中每个IPC结构都有的一个非负整数 的标识符,这样对一个消息队列发送消息时只要引用标识符就可以了。这个标识符是内核由IPC结构的关键字得到的,这个关键字,就是上面第一个函数的 key。数据类型key_t是在头文件sys/types.h中定义的,它是一个长整形的数据。在我们后面的章节中,还会碰到这个关键字。

当共享内存创建后,其余进程可以调用shmat()将其连接到自身的地址空间中。

void *shmat(int shmid, void *addr, int flag)

shmid为shmget函数返回的共享存储标识符,addr和flag参数决定了以什么方式来确定连接的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。

使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步,另外,可以通过使用shmctl函数设置共享存储内存的某些标志位如SHM_LOCK、SHM_UNLOCK等来实现。

2.3.4 信号量

信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:

(1) 测试控制该资源的信号量。

(2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。

(3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。

(4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。

维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include /linux /sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获 得一个信号量ID。

struct sem {

short sempid/* pid of last operaton */

ushort semval/* current value */

ushort semncnt/* num procs awaiting increase in semval */

ushort semzcnt/* num procs awaiting semval = 0 */

}

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag)

key是前面讲过的IPC结构的关键字,flag将来决定是创建新的信号量集合,还是引用一个现有的信号量集合。nsems是该集合中的信号量数。如果是创建新 集合(一般在服务器中),则必须指定nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将nsems指定为0。

semctl函数用来对信号量进行操作。

int semctl(int semid, int semnum, int cmd, union semun arg)

不同的操作是通过cmd参数来实现的,在头文件sem.h中定义了7种不同的操作,实际编程时可以参照使用。

semop函数自动执行信号量集合上的操作数组。

int semop(int semid, struct sembuf semoparray[], size_t nops)

semoparray是一个指针,它指向一个信号量操作数组。nops规定该数组中操作的数量。

下面,我们看一个具体的例子,它创建一个特定的IPC结构的关键字和一个信号量,建立此信号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数ftok生成我们上文所说的唯一的IPC关键字。

#include <stdio.h>

#include <sys/types.h>

#include <sys/sem.h>

#include <sys/ipc.h>

void main() {

key_t unique_key/* 定义一个IPC关键字*/

int id

struct sembuf lock_it

union semun options

int i

unique_key = ftok(".", 'a')/* 生成关键字,字符'a'是一个随机种子*/

/* 创建一个新的信号量集合*/

id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666)

printf("semaphore id=%d\n", id)

options.val = 1/*设置变量值*/

semctl(id, 0, SETVAL, options)/*设置索引0的信号量*/

/*打印出信号量的值*/

i = semctl(id, 0, GETVAL, 0)

printf("value of semaphore at index 0 is %d\n", i)

/*下面重新设置信号量*/

lock_it.sem_num = 0/*设置哪个信号量*/

lock_it.sem_op = -1/*定义操作*/

lock_it.sem_flg = IPC_NOWAIT/*操作方式*/

if (semop(id, &lock_it, 1) == -1) {

printf("can not lock semaphore.\n")

exit(1)

}

i = semctl(id, 0, GETVAL, 0)

printf("value of semaphore at index 0 is %d\n", i)

/*清除信号量*/

semctl(id, 0, IPC_RMID, 0)

}

semget()

可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

系统调用:semget()

原型:intsemget(key_t key,int nsems,int semflg)

返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)

EEXIST(信号量集已经存在,无法创建)

EIDRM(信号量集已经删除)

ENOENT(信号量集不存在,同时没有使用IPC_CREAT)

ENOMEM(没有足够的内存创建新的信号量集)

ENOSPC(超出限制)

系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:

#defineSEMMSL32/*<=512maxnumofsemaphoresperid*/

下面是一个打开和创建信号量集的程序:

intopen_semaphore_set(key_t keyval,int numsems)

{

intsid

if(!numsems)

return(-1)

if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)

{

return(-1)

}

return(sid)

}

}

==============================================================

semop()

系统调用:semop()

调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops)

返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)

EACCESS(权限不够)

EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)

EFAULT(sops指向的地址无效)

EIDRM(信号量集已经删除)

EINTR(当睡眠时接收到其他信号)

EINVAL(信号量集不存在,或者semid无效)

ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)

ERANGE(信号量值超出范围)

第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:

/*semop systemcall takes an array of these*/

structsembuf{

ushortsem_num/*semaphore index in array*/

shortsem_op/*semaphore operation*/

shortsem_flg/*operation flags*/

sem_num将要处理的信号量的个数。

sem_op要执行的操作。

sem_flg操作标志。

如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。

===============================================================

semctl()

系统调用:semctl()

原型:int semctl(int semid,int semnum,int cmd,union semunarg)

返回值:如果成功,则为一个正数。

如果失败,则为-1:errno=EACCESS(权限不够)

EFAULT(arg指向的地址无效)

EIDRM(信号量集已经删除)

EINVAL(信号量集不存在,或者semid无效)

EPERM(EUID没有cmd的权利)

ERANGE(信号量值超出范围)

系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命令,这样就要求有一个更为复杂的数据结构。

系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。

参数cmd中可以使用的命令如下:

·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。

·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/150443.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-03-21
下一篇2023-03-21

发表评论

登录后才能评论

评论列表(0条)

    保存