Linux原理及应用03.ppt_第1页
Linux原理及应用03.ppt_第2页
Linux原理及应用03.ppt_第3页
Linux原理及应用03.ppt_第4页
Linux原理及应用03.ppt_第5页
已阅读5页,还剩71页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、LINUX原理及应用,武汉大学计算机学院 郑鹏 Email:,第3章 Linux进程间通信,Linux下的进程通信手段基本上是从Unix平台上的进程通信手段继承而来。 Unix进程间通信包括: 信号 管道 System V IPC。System V IPC又包括: 消息队列 信号量 共享内存区,第3章 Linux进程间通信,Linux支持多种通信机制,常用的有:信号(signal)、管道(pipe)以及与SYSTEM V兼容的消息队列(message queue)、信号量(semaphore)和共享内存(shared memory)。Linux还支持用于不同机器之间的进程间通信套接字(sock

2、et)。,(1)信号机制,信号是Unix系统中的最古老的进程间通信方式,也是Linux最基本的进程通讯机制,用于通知接收进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身。信号机制的一个主要特点是它的异步特性,这表现在进程在执行期间可随时接收到信号,甚至可能当进程正在执行系统调用时接收信号。 Linux除了支持Unix早期信号语义函数signal外,还支持语义符合POSIX.1标准的信号函数sigaction。,(2)管道机制,管道是实现进程间大容量信息传送的机构。管道用于连接一个读进程和一个写进程,是实现二者之间通信的共享文件。管道可分为 匿名管道(anonymous p

3、ipe)。匿名管道可用于具有亲缘关系进程间的通信,它实际是由固定大小的高速缓冲区构成。 有名管道(named pipe)。有名管道克服了管道没有名字的限制,是一个按名存取的文件,该文件可长期存在,任意进程都可按通常的文件存取方法存取有名管道。有名管道除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。,(3)System V的进程通信机制,Linux支持三种SystemV进程通信机制:消息机制、信号量机制和共享内存机制。Linux对三种机制的实施大同小异。其中最快的一种形式是共享内存。 消息队列:消息队列是消息的链接表。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队

4、列中的消息。 共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。 信号量:主要作为进程间以及同一进程不同线程之间的同步手段。,(4)套接字(Socket),套接字是更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上。Linux和System V的变种都支持套接字。,3.1 信号,系统中有一组被详细定义的信号类型,这些信号可以由内核或者系统中其它具有适当权限的进程产生。信号可以发给一个或多个进程,也可以在某些系统错误发生时产生。信号还被shell进程用来向其子进程发送任务控制命令。为区

5、别于硬件中断,又称为软中断。 信号事件的发生有两个来源: 硬件来源,比如按下了键盘或者其它硬件故障; 软件来源,最常用发送信号的系统函数是kill,raise,alarm和setitimer以及sigqueue函数,软件来源还包括一些非法运算等操作。,3.1 信号,信号生命周期是从信号发送到相应的信号处理函数执行完毕。对于一个完整的信号生命周期来说,可以分为三个重要的阶段,这三个阶段由四个重要事件来刻画: 信号诞生:指的是触发信号的事件发生; 信号在进程中注册完毕; 信号在进程中的注销完毕; 信号处理函数执行完毕。 相邻两个事件的时间间隔构成信号生命周期的一个阶段。,3.1 信号,信号是进程间

6、通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。 可以从两个不同的分类角度对信号进行分类: (1)可靠性方面:可靠信号与不可靠信号; (2)与时间的关系上:实时信号与非实时信号。,3.1 信号,进程可以通过三种方式来响应一个信号: 忽略信号,即对信号不做任何处理,其中,有两个信号不能忽略:SIGKILL及SIGSTOP; 捕捉信号。定义信号处理函数,当信号发生时,执行相应的处理函数; 执行缺省操作,Linux对每种信号都规定了默认操作。注意,进程对实时信号的缺省反应是进

7、程终止。 Linux究竟采用上述三种方式的哪一个来响应信号,取决于传递给相应API函数的参数。,3.1 信号,信号与中断的相似点: (1)采用了相同的异步通信方式; (2)当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序; (3)都在处理完毕后返回到原来的断点; (4)对信号或中断都可进行屏蔽。 信号与中断的区别: (1)中断有优先级,而信号没有优先级,所有的信号都是平等的; (2)信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行; (3)中断响应是及时的,而信号响应通常都有较大的时间延迟。,3.1 信号,信号机制具有以下三方面的功能: (1)发送信号。发

8、送信号的程序用系统调用kill()实现; (2)预置对信号的处理方式。接收信号的程序用signal()来实现对处理方式的预置; (3)收受信号的进程按事先的规定完成对相应事件的处理。,3.1.1可靠信号与不可靠信号,把那些建立在早期机制上的信号叫做“不可靠信号”,信号值小于SIGRTMIN(SIGRTMIN=31,SIGRTMAX=63)的信号都是不可靠信号。这就是“不可靠信号”的来源。它的主要问题是: 进程每次处理信号后,就将对信号的响应设置为默认动作。在某些情况下,将导致对信号的错误处理。 信号可能丢失。 早期Unix的不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失。,3

9、.1.1可靠信号与不可靠信号,Linux下的不可靠信号问题主要指的是信号可能丢失。 信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigaction()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。,3.1.2实时信号与非实时信号,早期Unix系统只定义了32种信号,Ret hat7.2支持64种信号,编号0-63,将来可能进一步增加,这需要得到内核的支持。 前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都

10、有各自的缺省动作。 后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。实时信号是POSIX标准的一部分,可用于应用进程。 非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。,3.1.3与信号处理有关的函数,1信号的发送 信号的发送,是指由发送进程把信号送到指定进程的信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将它唤醒,发送进程就此结束。一个进程可能在其信号域中有多个位被置位,代表有多种类型的信号到达,但对于一类信号,进程却只能记住其中的某一个。 发送信号的主要函数有:kill()、raise()、sigque

11、ue()、alarm()、setitimer()以及abort()。 。,1信号的发送,(1)kill()函数格式如下: int kill(pid_t pid,int signo) 其中,pid是接收信号的进程标识符,参数signo是要发送的信号值。 当pid0时,内核将信号发送给进程pid; 当pid=0时,内核将信号发送给与发送进程同组的所有进程; 当pid=-1时,内核将信号发送给除发送进程自身外,所有进程pid大于1的进程。 当pid0 pid!=-1时,内核将信号发送给进程标识符为-pid的所有进程。 signo是信号值,当为0时(即空信号),实际不发送任何信号,可用于检查目标进程是

12、否存在,以及当前进程是否具有向目标发送信号的权限。root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号。 kill() 调用成功返回0;否则,返回-1。注:对于pid0时的情况,对于哪些进程将接受信号,各种版本说法不一。,1信号的发送,(2) raise()函数格式如下: int raise(int signo) 向进程本身发送信号,参数为即将发送的信号值。调用成功返回 0;否则,返回 -1。,1信号的发送,(3)sigqueue()函数格式如下: int sigqueue(pid_t pid, int signo, cons

13、t union sigval val) 调用成功返回0;否则,返回-1。 sigqueue()是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。 sigqueue的第一个参数是指定接收信号的进程ID,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。 sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。,1信号的发送,(4)alarm()函数格式如下: uns

14、igned int alarm(unsigned int seconds) 专门为SIGALRM信号而设,在指定的时间seconds秒后,将向进程本身发送SIGALRM信号,又称为闹钟时间。进程调用alarm后,任何以前的alarm()调用都将无效。如果参数seconds为零,那么进程内将不再包含任何闹钟时间。 返回值,如果调用alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。,1信号的发送,(5)setitimer()函数格式如下: int setitimer(int which, const struct itimerval *value, struc

15、t itimerval *ovalue); setitimer()比alarm功能强大,支持3种类型的定时器: ITIMER_REAL:设定绝对时间;经过指定的时间后,内核将发送SIGALRM信号给本进程; ITIMER_VIRTUAL:设定进程执行时间;经过指定的时间后,内核将发送SIGVTALRM信号给本进程; ITIMER_PROF:设定进程执行以及内核因本进程而消耗的时间和,经过指定的时间后,内核将发送ITIMER_VIRTUAL信号给本进程; setitimer()第一个参数which指定定时器类型(上面三种之一);第二个参数是结构itimerval的一个实例;第三个参数可不做处理。

16、 setitimer()调用成功返回0,否则返回-1。,1信号的发送,(6)abort()函数格式如下: void abort(void); 向进程发送SIGABORT信号,默认情况下进程会异常退出,当然可定义自己的信号处理函数。即使SIGABORT被进程设置为阻塞信号,调用abort()后,SIGABORT仍然能被进程接收。该函数无返回值。,2信号的安装,如果进程要处理某一信号,那么就要在进程中安装该信号。安装信号主要用来确定信号值及进程针对该信号值的动作之间的映射关系,即进程将要处理哪个信号;该信号被传递给进程时,将执行何种操作。 Linux主要有两个函数实现信号的安装: signal()

17、:只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装; sigaction():是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与sigqueue()系统调用配合使用。 当一个进程要进入或退出低优先级睡眠状态时,或即将从核心态返回用户态时,核心都要检查该进程是否已收到信号。当进程处于核心态时,即使收到信号也不理睬;只有当它返回到用户态后,才处理信号。,2信号的安装,(1)signal()函数格式 系统调用格式:signal(signo,function) 参数定义为: signal(signo

18、,function) int signo; void (*func) ( ) 其中signo用于指定信号的类型,为0则表示没有收到信号。 function:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号SIGKILL,SIGTRAP和SIGPWR以外的信号,核心自动地重新设置软中断信号处理程序的值为SIG_DFL,一个进程不能捕获SIGKILL信号。function 的解释如下: function=1时,进程对signo类信号不予理睬,亦即屏蔽了该类信号; function=0时,缺省值,进程在收到signo信号后应终止自己; function

19、为非0,非1类整数时,值即作为信号处理程序的指针。,2信号的安装,(2)sigaction()函数格式 int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact); sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程

20、会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。 第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。,3信号集及信号集操作函数,信号集被定义为一种数据类型: typedef struct unsigned long sig_NSIG_WORDS; sigset_t 信号集用来描述信号的集合,Linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。,3信号集及信号集

21、操作函数,下面是为信号集操作定义的相关函数: (1)int sigemptyset(sigset_t *set):初始化由set指定的信号集,信号集里面的所有信号被清空; (2)int sigfillset(sigset_t *set):调用该函数后,set指向的信号集中将包含linux支持的64种信号; (3)int sigaddset(sigset_t *set, int signum):在set指向的信号集中加入signum信号; (4)int sigdelset(sigset_t *set, int signum):在set指向的信号集中删除signum信号; (5)int sigis

22、member(const sigset_t *set, int signum):判定信号signum是否在set指向的信号集中。,4信号阻塞与信号未决,每个进程都有一个用来描述哪些信号递送到进程时将被阻塞的信号集,该信号集中的所有信号在递送到进程后都将被阻塞。下面是与信号阻塞相关的几个函数: (1)int sigprocmask(int how, const sigset_t *set, sigset_t *oldset):能够根据参数how来实现对信号集的操作。操作主要分三种: 当how= SIG_BLOCK时,在进程当前阻塞信号集中添加set指向信号集中的信号; 当how= SIG_UNB

23、LOCK时,如果进程阻塞信号集中包含set指向信号集中的信号,则解除对该信号的阻塞; 当how= SIG_SETMASK时,更新进程阻塞信号集为set指向的信号集。,4信号阻塞与信号未决,(2)int sigpending(sigset_t *set):获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。 (3)int sigsuspend(const sigset_t *mask):用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调

24、用始终返回-1,并将errno设置为EINTR。,5信号的接收,进程的task_struct中有一个signal域记录进程接收到的信号类型,共32位。当某位置为1时,表示收到了某类信号。Linux不提供处理多个同类信号的方式。即进程无法区分它是收到了1个还是4个SIGCONT信号。信号并非一产生就立刻处理,而是等到进程再次运行时才处理。,3.2 管道,所谓管道,是指能够连接一个写进程和一个读进程的、并允许它们以生产者消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程从管道的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。管道是半双工的,数据只能向一个方向

25、流动;需要双向通信时,需要建立起两个管道。 管道分为匿名管道和命名管道,它们都是通过内核缓冲区按先进先出的方式传输数据。在缓冲区读空或写满时,则由相应的规则控制读写进程进入等待队列,当空的缓冲区有写入数据或满的缓冲区有数据读出时,就唤醒等待队列中的读写进程继续读写。,3.2 管道,匿名管道是由pipe()系统调用创建。管道与两个文件描述符连接,fd0含有管道read()端的文件描述符,而fd1含有管道write()端的文件描述符。从匿名管道读数据是一次性操作,数据一旦被读,就释放其管道空间,以便写更多数据。一个匿名管道仅供具有共同祖先的两个进程之间共享,并且这个祖先必须已经建立了供它们使用的管

26、道。 命名管道(FIFO)是按先进先出方式传送信息。命名管道由mknod()系统调用创建先进先出文件FIFO。,3.2.1 匿名管道,Linux利用系统调用pipe()建立一匿名管道。 系统调用格式:pipe(filedes) 参数定义: int pipe(filedes); int filedes2; 其中,filedes1是写入端,用于写入数据,写入时必须关闭读取端,即close(filedes0)。filedes0是读出端,用于读出数据,读取时必须关闭写入端,即close(filedes1)。,3.2.1 匿名管道,Linux利用系统调用read()读一匿名管道。 系统调用格式:read

27、(fd,buf,nbyte) 功能:从fd所指示的文件中读出nbyte个字节的数据,并将它们送至由指针buf所指示的缓冲区中。如该文件被加锁,等待,直到锁打开为止。 Linux利用系统调用write()写一匿名管道。 系统调用格式:write(fd,buf,nbyte) 功能:把nbyte个字节的数据,从buf所指向的缓冲区写到由fd所指向的文件中。如文件加锁,暂停写入,直至开锁。参数定义同read()一样。 内核为地址设置一个读指针和一个写指针,按先进先出顺序读、写。每次进程在访问pipe文件前,都需检查该索引文件是否已被上锁。若是,进程便睡眠等待,否则,将其上锁,进行读/写。,3.2.1

28、匿名管道,匿名管道的局限是:只支持单向数据流;只能用于具有亲缘关系的进程之间;没有名字;管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。,3.2.2 有名管道,一个可以在文件系统中长期存在的、具有路径名的文件。用系统调用mknod()或mkfifo()建立。它克服匿名管道使用上的局限性,可让更多的进程也能利用管道进行通信。对有名管道的访问方式与访问其他文件一样,需先用open()打开。二种管道的读写方式是相同的。 int mkfi

29、fo(const char * pathname, mode_t mode) 该函数的第一个参数是一个普通的路径名,也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。,3.2.3管道的实现,在Linux中,管道通过指向同一个临时VFS inode的两个file数据结构来实现,此VFS ino

30、de指向内存中的一个物理页面。这样就隐藏了读写管道和读写普通的文件时系统调用的差别。当写入进程对管道写时,字节被拷贝到共享数据页面中,当读取进程从管道中读时,字节从共享数据页面中拷贝出来。Linux必须同步对管道的访问,保证读者和写者以确定的步骤执行,为此需要使用锁、等待队列和信号等同步机制。,3.2.3管道的实现,下图给出了两个进程用管道进行通信的实例。每个file数据结构指向不同的文件操作向量,一个是实现对管道的写,另一个从管道中读。,3.2.3管道的实现,进程向管道写数据时,可能有以下两种情况: (1)管道中有足够的空间存放要写的数据,此时每写一数据块后,核心便自动增加地址项的大小。写操

31、作完成后,核心修改索引节点中的写指针,并唤醒所有因该管道空而睡眠等待的读进程。 (2)管道中无足够的空间存放要写入的数据,此时核心对该索引节点作标记后让写进程睡眠,等待数据从管道中排出。上述情况的一个例外是,当进程写的数据量大于管道的容量时,核心将尽可能多的数据写到管道中,然后使进程睡眠,直到获得更多的空间。,3.2.3管道的实现,当进程从管道读数据时,同样会有两种情况: (1)管道中有足够的数据供进程读。此时,进程便从读指针所指位置开始读数据,每读出一个数据块后,便增加地址项的大小。读操作结束后,核心修改索引节点中的读指针,并唤醒所有睡眠的写进程。 (2)进程要读的数据比管道中的数据多。此时

32、,读进程将返回管道中当前所有的数据。如果管道为空,进程一般将进入睡眠,直到一个写进程将数据写入管道,再将读进程唤醒。,3.3 SYSTEM 进程间通信,Linux完全支持Unix System V中的3种IPC机制:消息队列、信号量、共享内存。系统V的IPC机制十分类似于文件,其使用过程是: (1)创建或打开IPC对象,获得该对象的引用描述符; (2)利用引用描述符使用IPC对象; (3)关闭或释放IPC对象。,3.3 SYSTEM 进程间通信,对象的引用描述符是它在对象表中的索引。每种IPC机制都有自己的一套数据结构,虽然这些结构的内容互不相同,但每种对象的数据结构中都包含一个ipc_per

33、m结构,它含有创建者的用户及组标志、所有者的用户及组标志、对象的存取模式以及IPC对象键(key),以确定一个IPC操作是否可访问该资源。ipc_perm结构描述对一个系统V IPC对象的存取权限。,3.3 SYSTEM 进程间通信,结构ipc_perm的成员mode的低九位定义了对该资源的访问许可,以确定一个执行了ipc系统调用的进程能否访问该资源。 struct ipc_perm key_t key; /* 键 */ ushort uid; /*所有者 uid*/ ushort gid; /*所有者 gid */ ushort cuid; /*创建者 uid */ ushort cgid;

34、 /*创建者 gid */ ushort mode; /*读/写权限*/ ushort seq; /*序列号*/ ;,3.3 SYSTEM 进程间通信,IPC对象键值被用来定位系统V IPC对象的引用标志符。这样的键值一共有两组: 公有:如果此键为公有,则系统中任何进程,只要通过了权限检查,就可以获得该键(key)所对应的IPC对象的引用描述符。系统V IPC对象不能直接用一个键值来引用,而只能使用引用描述符。 私有:则键值为0,说明每个进程都可以用键值0建立一个专供其私用的对象。 虽然Linux在3种IPC对象上定义了许多操作,但只提供了一个系统调用sys_ipc,通过该系统调用,可以实现所

35、有的IPC操作。,3.3.1 Linux的消息,消息(message)是一个格式化的可变长的信息单元。消息机制允许由一个进程给其它任意的进程发送一个消息。当一个进程收到多个消息时,可将它们排成一个消息队列。消息使用二种重要的数据结构: 消息首部:记录一些与消息有关的信息,如消息的类型、大小、指向消息数据区的指针、消息队列的链接指针等。 消息队列头表:其每一项作为一个消息队列的消息头,记录了消息队列的有关信息。,3.3.1 Linux的消息,核心把每个消息以msg structure的方式存放在队列里。在linux/msg.h里是如下定义的: struct msg struct msg *msg

36、_next; /*指向队列下一个消息的指针*/ long msg_type; char *msg_spot; /*指向消息头的指针*/ short msg_ts; /*消息文本或消息结构的长度*/ ; 消息队列就是一个消息的链表。每个消息队列都有一个队列头,用结构msg_queue来描述。队列头中包含了该消息队列的大量信息,包括消息队列键值、用户ID、组ID、消息队列中消息数目等等,甚至记录了最近对消息队列读写进程的ID。可以访问这些信息,也可以设置其中的某些信息。,3.3.1 Linux的消息,Linux维护着一个消息队列链表,其中每个元素指向一个描述消息队列的msqid_ds结构。当创建新

37、的消息队列时,系统将从系统内存中分配一个msqid_ds结构,同时将其插入到数组中。IPC对象的三种类型都有一个被内核管理的内部数据结构。对于消息队列,就是msqid_ds结构。系统建立的每个消息队列的这种结构都由内核来创建,存储和管理。,3.3.1 Linux的消息,msgqid_ds结构在linux/msg.h里定义如下:,struct msqid_ds struct ipc_perm msg_perm;/*一个ipc_perm结构的实例*/ struct msg *msg_first; /*指向队列的第一个消息(链表头)*/ struct msg *msg_last; /*指向队列的最后

38、一个消息(表尾)*/ time_t msg_stime; /*上一次 msgsnd 的时间*/ time_t msg_rtime; /*上一次 msgrcv 的时间*/ time_t msg_ctime; /*上一次修改时间*/ struct wait_queue *wwait; struct wait_queue *rwait; ushort msg_cbytes; ushort msg_qnum; /* 队列中消息数目 */ ushort msg_qbytes; /* 一条队列最大字节数 */ ushort msg_lspid; /*上一次 msgsnd 调用的 pid*/ ushort

39、msg_lrpid; /*上一次 msgrcv 调用的 pid*/ ;,1、消息的创建,Linux中,每一个消息队列都有一个称为关键字(key)的名字,是由用户指定的;消息队列有一消息队列描述符,其作用与用户文件描述符一样,也是为了方便用户和系统对消息队列的访问。 Linux利用系统调用msgget()创建一个消息,获得一个消息的描述符。内核将搜索消息队列头表,确定是否有指定名字的消息队列。若无,内核将分配一新的消息队列头,并对它进行初始化,然后给用户返回一个消息队列描述符,否则它只是检查消息队列的许可权便返回。,2、消息的发送,系统调用格式:msgqid=msgget(key,flag) 参

40、数定义: int msgget(key,flag) key_t key; int flag; 其中:key是用户指定的消息队列的名字;flag是用户设置的标志和访问方式,可以为以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或结果。如IPC_CREAT|0400,是否该队列已被创建,无则创建,是则打开;IPC_EXCL|0400是否该队列的创建应是互斥的。成功返回消息队列描述符msgqid,失败则返回-1。,2、消息的发送,Linux利用系统调用msgsnd()发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。 系统调用格式:msgsnd(ms

41、gqid,msgp,size,flag) 参数定义: int msgsnd(msgqid,msgp,size,flag) int msgqid,size,flag; struct msgbuf * msgp; 其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针。 size指示由msgp指向的数据结构中字符数组的长度;即消息的长度。 flag规定当核心用尽内部缓冲空间时应执行的动作:进程是等待,还是立即返回。,2、消息的发送,对于msgsnd( ),核心须完成以下工作: (1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回; (2)核

42、心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区; (3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据; (4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。,3、消息的接收,Linux利用系统调用msgrcv( )接收一消息。 系统调用格式:msgrcv(msgqid,msgp,size,type,flag) 参数定义: int msgrcv(msgqid,msgp,size,type,flag) int msgqid,size,flag; struct msgbuf *msg

43、p; long type; 该系统调用从msgqid代表的消息队列中读取一个消息,并把消息存储在msgp指向的msgbuf结构中。msgqid为消息队列描述字;消息返回后存储在msgp指向的地址,size指定msgbuf的mtext成员的长度(即消息内容的长度),type为请求读取的消息类型;读消息标志flag可以为以下几个常值的或: IPC_NOWAIT:如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG; IPC_EXCEPT:与type0配合使用,返回队列中第一个类型不为type的消息; IPC_NOERROR:如果队列中满足条件的消息内容大于所请求的size字节,则把

44、该消息截断,截断部分将丢失。,3、消息的接收,对于msgrcv系统调用,核心须完成下述工作: (1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回; (2)根据type的不同分成三种情况处理: type=0,接收该队列的第一个消息,并将它返回给调用者; type为正整数,接收类型type的第一个消息; type为负整数,接收小于等于type绝对值的最低类型的第一个消息。 (3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。,4、消息的控制,系统调用msgc

45、tl( )对消息队列进行操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。 系统调用格式:msgctl(msgqid,cmd,buf); 参数定义: int msgctl(msgqid,cmd,buf); int msgqid,cmd; struct msgqid_ds *buf; 其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类: (1)IPC_STAT。查询有关消息队列情况的命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;

46、 (2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等; (3)IPC_RMID。消除消息队列的标识符。,3.3.2 Linux共享存储区通信,共享存储区是Linux系统中通信速度最高的一种通信机制。该机制可使若干进程共享主存中的某一个区域,且使该区域出现(映射)在多个进程的虚地址空间中。 一个进程的虚地址空间中又可连接多个共享存储区,每个共享存储区都有自己的名字。当进程间欲利用共享存储区进行通信时,必须先在主存中建立一共享存储区,然后将它附接到自己的虚地址空间上。,3.3.2 Linux共享存储区通信,为了管理方便,

47、Linux维护了一个共享内存对象向量表shm_segs,其中保存了系统中所有的共享内存对象。该向量表中的每个元素是一个指向shmid_kernel结构的指针,其中有一个分量是shmid_ds结构。每个新创建的共享内存区域由一个shmid_ds结构来表示。 共享存储允许两个或多个进程共享一给定的存储区。因为数据不需要在客户和服务者之间复制,所以这是最快的一种IPC。使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。若服务者将数据放入共享存储区,则在服务者做完这一操作之前,客户不应当去取这些数据。通常,信号量被用来实现对共享存储存取的同步。,3.3.2 Linux共享存储区通信,内核为

48、每个共享存储段设置了一个shmid_ds结构。,struct shmid_ds struct ipc_perm shm_perm; struct anon_map shm_amp; /*内核中的指针*/ int shm_segsz; /*段尺寸*/ ushort shm_lkcnt; /*对段加锁的次数*/ ushort shm_cpid; /*创建者 pid */ ushort shm_lpid; /* 上一次操作的进程的 pid */ short shm_nattch; /* 目前附着的进程数目 */ time_t shm_atime; /* 上一次附着的时间 */ time_t shm_

49、dtime; /* 上一次分离的时间 */ time_t shm_ctime; /* 上一次修改的时间 */ ;,3.3.2 Linux共享存储区通信,共享内存机制主要用到了如下几个系统调用: (1) 创建共享内存段shmget()。 (2) 进程申请连接共享内存段shmat()。 (3) 进程取消与共享内存段的连接shmdt()。 (4) 取消一个共享内存段shmctl()。,1、创建、获得一个共享存储区,Linux利用系统调用shmget()来创建、获得一个共享存储区。 系统调用格式: shmid=shmget(key,size,flag) 参数定义: int shmget(key,siz

50、e,flag); key_t key; int size,flag; 其中,key是共享存储区的名字;size是其大小(以字节计);flag是用户设置的标志,如IPC_CREAT。IPC_CREAT表示若系统中尚无指名的共享存储区,则由核心建立一个共享存储区;若系统中已有共享存储区,便忽略IPC_CREAT。,2、共享存储区的附接,Linux利用系统调用shmat()实现共享存储区的附接。从逻辑上将一个共享存储区附接到进程的虚拟地址空间上。 系统调用格式:virtaddr=shmat(shmid,addr,flag) 参数定义 char *shmat(shmid,addr,flag); int

51、 shmid,flag; char * addr; 其中,shmid是共享存储区的标识符;addr是用户给定的,将共享存储区附接到进程的虚地址空间;flag规定共享存储区的读、写权限,以及系统是否应对用户规定的地址做舍入操作。该系统调用的返回值是共享存储区所附接到的进程虚地址viraddr。,3、共享存储区的分离,Linux利用系统调用shmdt()把一个共享存储区从指定进程的虚地址空间分离。 系统调用格式:shmdt(addr) 参数定义 int shmdt(addr); char addr; 其中,addr是要分离连接的虚地址,亦即以前由连接的系统调用shmat()所返回的虚地址。调用成功

52、时,返回0值,调用不成功,返回-1。,4、共享存储区的控制,Linux利用系统调用shmctl()共享存储区的控制,对其状态信息进行读取和修改。 系统调用格式:shmctl(shmid,cmd,buf) 参数定义 int shmctl(shmid,cmd,buf); int shmid,cmd; struct shmid_ds *buf; 其中,buf是用户缓冲区地址,cmd是操作命令。命令可分为多种类型: (1)用于查询有关共享存储区的情况。 (2)用于设置或改变共享存储区的属性。 (3)对共享存储区的加锁和解锁命令; (4)删除共享存储区标识符等。,3.3.3 Linux的信号量,为了管理

53、方便,Linux将系统中所有的信号量组织到一个向量表semary中。向量表中的每个元素都是一个指向semid_ds结构的指针,每个semid_ds结构描述一个信号量对象。semid_ds结构的定义如下:,struct semid_ds struct ipc_perm sem_perm; /信号量对象的认证信息 _ _kernel_time_t sem_otime; /最后一次在该信号量上执行操作的时间 _ _kernel_time_t sem_ctime; /信号量对象最后一次改变的时间 struct sem *sem_base; /信号量数组 struct sem_queue *sem_pe

54、nding; /等待操作的队列 struct sem_queue *sem_pending_last; /等待操作的队列 struct sem_undo *undo; /信号量对象的调整队列 unsigned short sem_nsems; ,3.3.3 Linux的信号量,sem结构描述一个具体的信号量。定义如下:,struct sem int semval; /* 信号量值 */ int sempid /* 上一次操作的进程的 pid */ ushort semncnt; /* 等待增加 semval 值的进程数目 */ ushort semzcnt; /* 等待 semval = 0

55、的进程数目 */ ;,1.创建/打开信号量,在程序使用信号量之前,必须首先创建信号量,如果信号量已经存在,可打开。创建/打开信号量的函数原形如下所示: int semget(key_t key,int nsems,int semflg); 参数说明: (1)key:在本地系统中表示要创建或者访问的信号量集的关键字,当然为了避免与其他的信号量产生冲突,可以简单的利用IPC_PRIVATE来表示一个新建的信号量。 (2)nsems:要创建或者要访问的信号量集中信号量的数目。 (3)semflg:指定不同的选项和权限位的标志,只有在信号量集不存在时创建。可以为IPC_CREATE,IPC_EXCL。

56、 该函数执行成功时,返回信号量句柄;失败时,返回-1。,2.信号量的控制,该函数执行成功时,返回相应的值;失败时,返回-1。,int semctl(int semid, /信号量集的句柄 int semnum, /信号量集的元素数 int cmd, /命令 union semun arg); union semun int val; struct semid_ds *buf; ushort *array; agc;,3.等待/通知信号量,等待信号量和通知信号量都是利用函数semop(),只是相关的参数不同而已。 如果sem_op为正,表示资源被释放,信号量增加。 如果sem_op为负,表示资源被申请,信号量减少。 如果sem_op为0,表示进程被阻塞直到信号量变为0。,int semop(int semid,struct sembuf *sops, /指向元素操作数组 unsigned short nsops/数组中元素操作的个数); struct sembuf short sem_num; /*信号量的索引*/ short sem_op; /*正数表示通知信号量,负数表示等待信号量*/ short sem_flg;/*相关的操作标记*/ ;,4.信号量的P,V操作,在Linux中用down函数来完成操作。do

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论