Presentation is loading. Please wait.

Presentation is loading. Please wait.

《操作系统设计与实现》 Linux系统编程.

Similar presentations


Presentation on theme: "《操作系统设计与实现》 Linux系统编程."— Presentation transcript:

1 《操作系统设计与实现》 Linux系统编程

2 Linux系统编程 I 进程控制编程 II 进程间的通信

3 Linux系统编程 I 进程控制编程 II 进程间的通信

4 1 进程控制编程 进程的创建 system函数 exec()函数族替换进程 wait()和waitpid()函数

5 进程的创建 ps命令 功能 命令ps查阅进程状态(process status)(实际上就是将内核中proc[]和user[]数组的内容有选择地打印出来) 选项 用于控制列表的行数(进程范围)和列数(每进程列出的属性内容) 无选项:只列出在当前终端上启动的进程 e选项:列出系统中所有的进程(进程范围) t选项:列出指定终端上的所有进程(进程范围) f选项:以full格式列出每一个进程(控制列的数目) l选项:以long格式列出每一个进程(控制列的数目) 5

6 进程的创建 ps命令 命令ps列出的进程属性 UID:用户ID(注册名) PID:进程ID C:CPU占用指数 PPID:父进程的PID
STIME:启动时间 SZ:进程逻辑内存大小(Size) TTY:终端的名字 COMMAND:命令名 WCHAN:进程睡眠通道(Wait Channel) TIME:累计执行时间(占用CPU的时间) PRI:优先级 S:状态,S(Sleep),R(Run), Z(Zombie) 6

7 进程的创建 fork()函数 (1)头文件: #include <sys/types.h> //提供类型pid_t的定义
#include <unistd.h> (2)函数原型: pid_t fork( void ); (3)函数的返回值: 子进程中返回0,父进程中返回子进程ID,出错返回-1。 (4)函数说明: 一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID。 7

8 进程的创建 fork()函数 操作演示 fork.c
8

9 进程的创建 vfork()函数 (1)头文件: #include <sys/types.h> //提供类型pid_t的定义
#include <unistd.h> (2)函数原型: pid_t vfork( void ); (3)函数的返回值: 子进程中返回0,父进程中返回子进程ID,出错返回-1。 9

10 进程的创建 vfork()函数 fork与vfork的差异:
vfork()创建的进程并不将父进程的地址空间完全复制到子进 程中,因为子进程会立即调用exec(或exit),于是也就不会存 放该地址空间。相反,在子进程调用exec或exit之前,它在父 进程的空间进行。 10

11 进程的创建 vfork()函数 fork与vfork的差异:
vfork保证子进程先运行,在调用exec或exit之前与父进程数据是 共享的,在它调用exec或exit之后父进程才可能被调度运行; fork的父子进程是同级别的,没有前后限制。vfork保证子进程先 运行,如果子进程依赖于父进程的进一步动作,则会导致死锁。 vfork函数是通过允许父子进程可访问相同物理内存从而伪装了对进程地址空间的真实拷贝,当子进程需要改变内存中数据时才复制父进程。这就是著名的“写操作时拷贝”(copy-on-write)技术 11

12 进程的创建 vfork()函数 vfork.c 操作演示 12

13 system( )函数 system()函数可以在进程中开始某一进程,并使用系统函数库来创建新进程。 (1)头文件:
#include <stdlib.h> (2)函数原型: int system( const char *string ); (3)函数的返回值: 若system()在调用/bin/sh失败则返回127,其他失败原因返回-1 system()会调用fork()产生子进程,由子进程来调用/bin/sh –c string来执行参数string字符串所代表的命令,此命令执行完后随即返回原调用的进程。 13

14 system( )函数 system.c 操作演示 程序在ls命令完成后,返回原调用的进程,所以最后有Done输出! 14

15 exec( )函数族替换进程 exec()函数族的作用是根据指定的文件名找到可执 行文件,并用它来取代调用进程的内容,换句话说,就 是在调用进程内部执行一个可执行文件。这里的可执行 文件既可以是二进制文件,也可以是任何Linux下可执 行的脚本文件。

16 exec( )函数族替换进程 exec()系统调用功能 父进程创建子进程后,子进程一般要调用一种exec函数以执行 不同的程序。
当进程调用一种exec函数时,该进程执行的程序完全替换为新 程序,而新程序从其main函数开始执行。 调用exec并不创建新进程,所以前后的进程ID并不变化,只是 将当前进程重新初始化了数据段、代码段和堆栈段,在执行完 之后,原调用进程的内容除了进程号外,其他全部被新的进程 替换了。 16

17 exec( )函数族替换进程 (1)exec()函数原型 在Linux中,exec指的是一组函数,共有6种调用形式, 它们的声明格式如下:
(2)函数返回值 函数调用出错时返回-1;函数调用成功不返回值。 int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char *const envp[ ]); int execv(const char *path, char *const argv[ ]); int execvp(const char *file, char *const argv[ ]); int execve(const char *path, char *const argv[ ], char *const envp[ ]); 17

18 wait( )和waitpid( )函数 在unix/linux中,正常情况下,子进程是通过父进程创 建的,子进程在创建新的进程。
子进程的结束和父进程的运行是一个异步过程,即父进 程永远无法预测子进程 到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要 调用wait()或者waitpid()系统调用取得子进程的终止状 态。

19 wait( )和waitpid( )函数 僵尸进程:一个进程使用fork创建子进程,如果子进 程退出,而父进程并没有调用wait或waitpid获取子进 程的状态信息,那么子进程的进程描述符仍然保存在 系统中。这种进程称之为僵尸进程。 孤儿进程:一个父进程退出,而它的一个或多个子进 程还在运行,那么那些子进程将成为孤儿进程。孤儿 进程将被init进程(进程号为1)所收养,并由init进程对 它们完成状态收集工作。

20 wait( )和waitpid( )函数 僵尸进程的危害
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了 init进程身上,每当出现一个孤儿进程的时候,内核就把孤儿进 程的父进程设置为init,而init进程会循环地wait()它的已经退出 的子进程。

21 wait( )和waitpid( )函数 通过示例观察僵尸进程的出现: look_zombie.c 操作演示 运行:ps -axl

22 wait( )和waitpid( )函数 wait( )
函数原型 #include<sys/types.h> #include<sys/wait.h> pid_t wait(int * status); 函数参数 自变量status用来表示子进程结束的状态值。 若不要结束状态值,则可以设成NULL 函数返回值 成功则返回子进程标识符(PID) 失败;返回-1 wait()函数会暂停父进程的运行,使其处于等待状态,一旦子进 程运行完,等待中的父进程就会重新运行。 如果有多个子进程在运行,则父进程在调用wait()函数后,会在 第一个子进程结束并返回时恢复父进程的执行。

23 wait( )和waitpid( )函数 wait( )
函数说明:WAIT()函数在运行之后,子进程结束状态值返回并保存 到status自变量中,要判断结束状态,可以利用下面的宏。 说明 WIFEXITED(status) 如果子进程正常结束,则返回非0值 WEXITSTATUS(status) 取得子进程由exit()返回的结束代码 WIFSIGNALED(status) 若子进程是因信号来到而结束,则返回ture WTERMSIG(status) 取得子进程因信号而中止的信号代码 WIFSTOPPED(status) 若子进程处理暂停运行状态,则返回ture WSTOPSIG(status) 取得引发子进程暂停的信号代码

24 wait( )和waitpid( )函数 waitpid( )函数 函数原型 #include<sys/types.h>
#include<sys/wait.h> pid_t waitpid(pid_t pid,int * status,int options); 函数参数 pid:准备等待的子进程标识符 status:表示子进程结束状态值,用宏来判断,若不在意结束状态值,则可以设成NULL options:选项值,指定调用waitpid()的行为 函数返回值 成功:返回子进程标识符(PID) 失败:返回-1 waitpid()函数会暂停当前进程的运行,使其处于等待状态,一旦子进程运 行完,等待中的父进程就会重新运行。 options 意义 WNOHANG 如果没有任何已经结束的子进程,马上返回,不予等待 WUNTRACED 如果子进程暂停运行则马上返回

25 wait( )和waitpid( )函数 wait()与waitpid()函数的区别
如果父进程的所有子进程都还在运行,调用wait将使父进程阻 塞,而调用waitpid时如果options参数中指定WNOHANG可以使 父进程不阻塞而立即返回0; wait等待第一个终止的子进程,而waitpid可以通过pid参数指定 等待哪一个子进程;

26 wait( )和waitpid( )函数 实例 eg1.wait_1.c wait()函数 代码实例 eg2.wait_2.c
eg1.waitpid3.c waitpid()函数 eg2.waitpid.c eg3.waitpid_m.c

27 Linux系统编程 I 进程控制编程 II 进程间的通信

28 进程间的通信 Linux下的进程通信手段基本上是从UNIX平台上的进程通信手段继承而来的。而对UNIX发展做出重大贡献的两大主力AT&T的贝尔实验室及BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧重点有所不同。 前者是对UNIX早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内; 后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。 而Linux则把两者的优势都继承了下来。 28

29 进程间的通信 UNIX进程间通信(IPC)方式包括管道、FIFO以及信号。
System V进程间通信(IPC)包括System V消息队列、System V信号量以及System V共享内存区。 Posix 进程间通信(IPC)包括Posix消息队列、Posix信号量以及Posix共享内存区。 29

30 进程间通信方式的种类 (1)管道(Pipe)及命名管道(named pipe):管道可用于具有亲缘 关系进程间的通信,命名管道,除具有管道所具有的功能外,它还允许 无亲缘关系进程间的通信。 (2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它 是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一 个信号与处理器收到一个中断请求效果上可以说是一样的。 (3)消息队列(Messge Queue):消息队列是消息的链接表,包括 Posix消息队列SystemV消息队列。它克服了前两种通信方式中信息量有 限的缺点,具有写权限的进程可以按照一定的规则向消息队列中添加新 消息;对消息队列有读权限的进程则可以从消息队列中读取消息。 30

31 进程间通信方式的种类 (4)共享内存(Shared memory):可以说这是最有用的进程间通信 方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看 到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同 步机制,如互斥锁和信号量等。 (5)信号量(Semaphore):主要作为进程之间以及同一进程的不同 线程之间的同步和互斥手段。 (6)套接字(Socket):这是一种更为一般的进程间通信机制,它可 用于网络中不同机器之间的进程间通信,应用非常广泛。 31

32 进程间的通信 1 管道通信 2 FIFO管道 3 共享内存 4 消息队列 32

33 1 管道通信 管道(PIPE)是Linux中最常见的IPC机制,它实际上是在进程间开 辟一个固定大小的缓冲区,需要发布信息的进程运行写操作,需 要接受信息的进程运行读操作。管道是单向的字节流,它把一个 进程的标准输出和另一个进程的标准输入连接在一起。 例:ls –l | sort 建立了这样一个管道:获取ls的输出,再将它作为sort命令的输入。形象地说,就是数据沿着管道从左边流到了右边。 33

34 1 管道通信 管道的特点 管道式半双工的,数据只能单向流动;需要相互通信时,就要建 立两个管道。
只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者 兄弟进程之间,有名管道则突破了这一限制)。 管道构成一种独立的文件系统,对于它的读写也可以使用普通的 read()和write()等函数。但是它不是普通的文件,并不属于其他任何 文件系统,并且只存在于内核的内存空间中。 数据的读出和写入都是单向的。一个进程向管道中写的数据被管 道另一端的进程读出。写入的数据每次都添加在管道缓冲区的末尾, 并且每次都是从缓冲区的头部读出数据。 34

35 1 管道通信 管道的创建函数 参数fd是一个二元整型数组,用于存放调用该函数所建管道的两个文件描述符fd[0]和fd[1]
函数原型 #include<unistd.h> int pipe(int fd[2]) 函数参数 数组fd[2]是管道的两个文件描述符 函数返回值 成功:返回0;失败:返回-1; 参数fd是一个二元整型数组,用于存放调用该函数所建管道的两个文件描述符fd[0]和fd[1] 管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1],其中fd[0]固定用于读管道,而fd[1]固定用于写管道,这样就构成了一个半双工的通道。 创建管道可以通过调用pipe()来实现。 35

36 1 管道通信 管道的操作 管道的读写使用的是文件描述符,而不是文件流,所以必须使用底层系统调用函数来访问数据。 (1)写管道
ret = write(fd[1], buf,n); 若管道已满,则被阻塞,直到管道另一端read将管道数据取走 (2)读管道 ret = read ( fd[0],buf,n); 若管道为空且文件描述符未关闭,则阻塞。 (3)关闭管道 colse(fd[0])/colse(fd[1]) 管道关闭时只需使用普通的close()函数逐个关闭各个文件描述符。 36

37 1 管道通信 父子进程之间管道通信 用pipe()函数创建的管道两端处于一个进程中,由于管道是主要用 于在不同进程间通信的,因此这在实际应用中没有太大意义。实 际上,通常先是创建一个管道,再通过fork()函数创建一子进程, 该子进程会继承父进程所创建的管道 。 37

38 1 管道通信 父子进程之间管道通信 父子进程分别拥有自己的读写通道,为了实现父子进程之间的读 写,只需把无关的读端或写端的文件描述符关闭即可。此时,父 子进程之间就建立起了一条“子进程写入父进程读取”的通道。 38

39 1 管道通信 ???标准I/O流管道 父子进程之间管道通信 现在要创建子进程,实现子进程通过管道读取父进程传送过来的数据! 代码实例
pipe.c ???标准I/O流管道 39

40 2 FIFO管道 管道(无名管道)只能用于具有亲缘关系的进程之间,这就大大地限 制了管道的使用。有名管道的出现突破了这种限制,它可以使互不相 关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在 文件系统中是可见的。 在建立了管道之后,两个进程就可以把它当作普通文件一样进行 读写操作,使用非常方便。不过值得注意的是,FIFO是严格地遵循先 进先出规则的,对管道及FIFO的读总是从开始处返回数据,对它们的 写则把数据添加到末尾,它们不支持如lseek()等文件定位操作。 FIFO管道的信息由于是存在于真实文件中,所以它并不会因为进 程的消失而消失。 40

41 2 FIFO管道 mkfifo命令 Mkfile [OPTION] NAME [ … ]
Mkfifo命令会创建一个名为name的FIFO,参数OPTION通常为-m mode,其中mode表示新建FIFO保存权限。 Fifo文件在磁盘上并没有数据块,仅用来标识内核中的一条通道,各个进程 可以打开这个文件进行read()/write(),实际上是在读写内核通道,这样就实 现了进程间的通信。 41

42 2 FIFO管道 Fifo的大小是一个需要考虑的重要因素,FIFO受系统限制,用来指 定在任何时间有多少数据放在FIFO文件中。
FIFO的定义在limits.h头文件中,其宏名为PIPE_BUF 在ubuntu系统中,此文件的大小通常为4076B

43 2 FIFO管道 mkfifo( )函数 函数原型 #include<sys/types.h>
#include<sys/state.h> int mkfifo( const char * filename, mode_t mode ) 函数参数 filename:要创建的管道 mode :FIFO的读写权限 函数返回值 成功:返回0; 失败:返回-1,错误原因存于errno中 mode值 说明 O_RDONLY 读管道 O_WRONLY 写管道 O_RDWR 读写管道 O_NONBLOCK 非阻塞 O_CREAT 如果该文件不存在,就创建一个新的文件,并用第三个参数为其设置权限 O_EXCL 如果使用O_CREAT时,文件存在,则可返回错误消息

44 2 FIFO管道 创建管道成功之后,就可以使用open()、read()和write()这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在open()中设置O_RDONLY,对于为写而打开的管道可在open()中设置O_WRONLY. 但打开FIFO的一个限制是进程无法以O_RDWR模式来打开FIFO,因为一般来说,FIFO是单向传送数据。若要在进程之间进行双向的数据传输,则使用一组FIFO,一个负责读,另一个负责写。 open打开FIFO的方式说明 mode值 说明 open(const char *path,O_RDONLY) 调用open( )的主进程会处于等待状态,直到其他进程打开相同的FIFO进行“写入”后才返回 open(const char *path,O_RDONLY|O_NONBLOCK) 调用open( )后会运行读的操作并立即返回主进程 open(const char *path,O_WRONLY) 调用open( )的主进程会处于等待状态,直到其他进程打开相同的FIFO进行读取后才返回 open(const char *path,O_WRONLY|O_NONBLOCK) 调用open( )后会执行写的操作并立即返回主进程

45 2 FIFO管道 FIFO管道实例-1 该实例包含了两个程序,一个用于读管道,另一个用于写管道。其中,在写管道的程序里创建管道,并且作为main( )函数里的参数由用户输入要写入的内容。 读管道读出了用户写入管道的内容,这两个函数用的是非阻塞读写管道。 代码实例 Fifo_read.c Fifo_write.c 45

46 2 FIFO管道 FIFO安全问题 前面的例子是两个进程之间的通信问题,也就是说,一个进程向FIFO文件写数据,而另一个进程则在FIFO文件中读取数据。如果有多个进程同时向同一个FIFO文件写数据,而只有一个读FIFO进程在同一个FIFO文件中读取数据时,一般会发生数据块的相互交错。 为了解决这一问题,就是让写操作的原子化。 系统规定:在一个以O_WRONLY(即阻塞方式)打开的FIFO中, 如果写入的数据长度小于等待PIPE_BUF,那么或者写入全部字节,或者一个字节都不写入。如果所有的写请求都是发往一个阻塞的FIFO的,并且每个写请求的数据长度小于等于PIPE_BUF字节,系统就可以确保数据决不会交错在一起。 46

47 2 FIFO管道 管道与FIFO对比 使用匿名管道,则通信的进程之间需要一个父子关系,通信的两个进程一定是由一个共同的祖先进程启动。但是匿名管道没有上面说到的数据交叉的问题。 与使用匿名管道相比,FIFO管道实例中的两个进程是没有什么必然的联系的,它们仅仅都访问同一个FIFO文件。它解决了之前在匿名管道中出现的通信的两个进程一定是由一个共同的祖先进程启动的问题。但是为了数据的安全,很多时候要采用阻塞的FIFO,让写操作变成原子操作。 47

48 2 FIFO管道 FIFO管道实例-2 阻塞方法 代码实例 fifo_block_w.c fifo_block_r.c 48

49 3 共享内存 共享内存:多个进程间共同使用同一段物理内存空间,通过将同一段物理内存映射到不同进程的虚空间中来实现的。由于映射到不同进程的虚空间中,不同进程可以直接读写内存,不需要进行内存的复制,所以共享内存的效率很高。 同一块物理内存被映射到进程A、B各自的进程地址空间。进程A能够看到进程B对共享内存中数据的更新。由于多个进程共享同一块内存区域,必然需要某种同步机制,比如:互斥锁、信号量等

50 3 共享内存 标识符和键 消息队列、信号量和共享内存,都属于内核中的IPC结构,它们都用标识符来描述。
标识符是IPC对象的内部名,而它的外部名则是key(键)。 调用ftok( )函数可以将一个路径名和项目ID(0~255间的字符值)变换为一个键key。 函数原型 #include<sys/ipc.h> key_t ftok( const char * path, int id) 函数参数 path:现存文件 id:子序号,0~255间的字符值 函数返回值 成功:返回共享内存段标识符 失败:返回-1

51 3 共享内存 共享内存实现方法 创建共享内存。通过shmget( )函数从内存中获得或创建一个IPC共享内存区域,并返回相应的标识符。
映射共享内存。通过shmat( )函数将创建的共享内存映射到具体的进程空间中去。该函数返回共享内存在进程空间中的地址,用户可以通过指针,使用不带缓冲的I/O读写这块共享内存。 撤销映射。在对共享内存读写完毕后,通过shmdt( )函数将共享内存从进程空间分离出去。

52 3 共享内存 相关重要函数 shmget( )函数用于创建一个新的共享内存段或存取一个已存的共享内存段。 函数原型
int shmget( key_t key, int size , int shmflg) 函数参数 key:键值 size:共享内存区大小 shmflg:权限标志,与open函数的mode参数一样,比如:0644 函数返回值 成功:返回共享内存段标识符 失败:返回-1 函数说明: 如果没有共享内存与键值key相对应,并且shmflg中包含IPC_CREAT标志位,就创建一个新的共享内存。 Key参数设置为IPC_PRIVATE, 创建一个当前进程的私有共享内存。

53 3 共享内存 相关重要函数 第一次创建完共享内存,不能被任何进程访问,shmat( ) 函数的作用就是用来启动对该共享内存的访问,并将共享内存连接到一个进程的逻辑地址空间中。 函数原型 char * shmat( int shmid, const void *shmadder, int shmflg) 函数参数 shmid:要映射的共享内存标识符 shmadder:指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。 shmflg:是一组标志位。默认取值为0,表示共享内存可读写; 取值SHM_RDONLY表示共享内存只读; 函数返回值 若映射共享内存成功:则返回进程空间中被映射的区域地址; 失败:返回-1

54 3 共享内存 相关重要函数 shmdt( ) 函数用于将共享内存从当前进程中分离。注意,将共享内存分离并不是删除它,只是使该共享内存对当前进程不再可用。 函数原型 int shmdt( const void * shmaddr) 函数参数 shmaddr: 被映射的共享内存地址段 函数返回值 成功:返回0; 失败:返回-1;

55 3 共享内存 相关重要函数 进程与共享内存建立映射之后,shmctl ( ) 函数可以用来控制共享内存。 函数原型
int shmctl( int shmid,int command, struct shhmid_ds *buf) 函数参数 shmid:被映射的共享内存标识符 cmd: 要采取的操作 buf: 是一个指针,指向共享内存模式和访问权限的结构 函数返回值 成功:返回0; 失败:返回-1; cmd值 说明 IPC_STAT 把shmid_ds结构中的数据结构设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。 IPC_SET 如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值 IPC_RMID 删除共享内存段 struct shmid_ds { uid_t shm_perm.uid; uid_t shm_perm.gid; mode_t shm_perm.mode; }; 例如:shmctl( shmid, IPC_RMID, 0) //删除shmid指示的共享内存

56 3 共享内存 实例 利用share_w.c创建共享内存,并向共享内存中写入数据hello data。由于程序的key值在共享内存中已经存在,share_r.c直接打开该共享内存并建立映射,读取共享内存的数据。 代码实例 share_w.c share_r.c

57 4 消息队列 消息队列(message queue)是系统内核地址空间中的一个内部的链表,即一系列连续排列的消息。
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。 消息队列与命名管道一样,每个数据块都有一个最大长度的限制。Linux用宏MSGMAX和MSGMNB来限制一条消息的最大长度和一个队列的最大长度。

58 4 消息队列 消息队列的实现方法 消息队列的实现包括创建或打开消息队列、添加消息、读取消息和控制消息队列这四种操作。
创建或打开消息队列使用的函数是msgget(),这里创建的消息队列的数量会受到系统消息队列数量的限制; 添加消息使用的函数是msgsnd()函数,它把消息添加到已打开的消息队列末尾; 读取消息使用的函数是msgrcv(),它把消息从消息队列中取走,与FIFO不同的是,这里可以指定取走某一条消息; 控制消息队列使用的函数是msgctl(),它可以完成多项功能。

59 4 消息队列 消息队列的操作函数 msgget( )函数用来创建和访问一个消息队列 函数原型
int msgget(key_t key , int msgflg) 函数参数 key:键值 msgflg:标志位 函数返回值 成功:返回消息队列ID; 失败:返回-1; 函数说明: 参数key是一个键值,由ftok()提供。 msgflg是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。

60 4 消息队列 消息队列的操作函数 msgsnd( )函数用来把消息添加到消息队列中 函数原型
struct msgbuf { long mtype //消息类型 char mtext[1] //消息正文 } 消息队列的操作函数 msgsnd( )函数用来把消息添加到消息队列中 函数原型 int msgsnd(int msqid ,const void *prt,size_t size,int msgflg) 函数参数 msqid :由msgget函数返回的消息队列标识符 prt:指向消息结构的指针 size:消息的字节数 msgflg IPC_NOWAIT: 若消息并没有立即发送而调用进程会立即返回 0: msgsnd调用阻塞直到条件满足为止 函数返回值 成功:返回0; 失败:返回-1; 函数说明: 向msgid代表的消息队列发送一个消息,即将发送的消息存储在prt指向的msgbuf结构中,消息的大小由size指定。

61 4 消息队列 消息队列的操作函数 msgrcv( )函数用来从一个消息队列获取消息。 函数原型
int msgrcv(int msgid, struct msgbuf *msgp, int size, long int msgtype, int msgflg) 函数参数 msqid :消息队列的队列ID msgp:消息缓冲区 size:消息字节数 msgtype 0:获取队列中的第一个消息 大于0:获取消息队列中第一个类型值为msgtype的消息 小于0:获取消息队列中第一个类型值不小于msgtype绝对值且类型值又最小的消息 函数返回值 成功时返回:放到接收缓存区中的字节数,消息被复制到由msgp指向的用户分配的缓存区中,然后删除消息队列中的对应消息。 失败时返回:-1.

62 4 消息队列 消息队列的操作函数 msgctl( )函数用来控制消息队列,它与共享内存的shmctl函数相似 函数原型
int msgctl(int msgid , int command , struct msgid_ds *buf) 函数参数 msqid :消息队列的队列ID msgp:要采取的动作 buf:消息队列缓冲区 函数返回值 成功时返回0,失败时返回-1. cmd值 说明 IPC_STAT 读取消息队列的数据结构msgid_ds ,并将其存储在buf指定的地址中 IPC_SET 设置消息队列的数据结构中的ipc_perm元素的值,这个值取自buf参数 IPC_RMID 删除消息队列;

63 4 消息队列 消息队列的实现 编写两个程序,message_send和message_recive来分别发送和接受信息。
注意:不同消息类型接收问题! 代码实例 message_s.c message_r.c

64 4 消息队列 消息队列与FIFO管道的比较 消息队列与FIFO管道一样,都可以在不相关的进程间通信,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制。 与命名管道相比,消息队列的优势在于: 1、消息队列可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。 2、通过发送消息可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。 3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。

65 Thanks for your time! Questions & Answers


Download ppt "《操作系统设计与实现》 Linux系统编程."

Similar presentations


Ads by Google