第六讲 unix 进程通信

107
第第第 第第第 UNIX UNIX 第第第第 第第第第

Upload: delilah-perry

Post on 14-Mar-2016

77 views

Category:

Documents


5 download

DESCRIPTION

第六讲 UNIX 进程通信. 注意:后期课程安排. 第 13 周: 星期二 7-8 节 上课(进程通信) 星期三 5-6 节 上机(进程通信) 星期五 1-2 节 上课( SOCKET) 第 14 周 星期二 7-8 节 上课( SOCKET) 第 15 周 星期二 5-6 节 上机 (实验检查) 星期五 1-2 节 考试. 1 基本概念. 通信分为两类: 控制信息的传递: 低级通信 大批量数据的传递: 高级通信. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: 第六讲  UNIX 进程通信

第六讲 第六讲 UNIXUNIX 进程通信进程通信

Page 2: 第六讲  UNIX 进程通信

注意:后期课程安排注意:后期课程安排第 13 周: 星期二 7-8 节 上课(进程通信) 星期三 5-6 节 上机(进程通信) 星期五 1-2 节 上课( SOCKET)第 14 周 星期二 7-8 节 上课( SOCKET)第 15 周 星期二 5-6 节 上机 (实验检查) 星期五 1-2 节 考试

Page 3: 第六讲  UNIX 进程通信

1 1 基本概念基本概念1 通信分为两类: 控制信息的传递: 低级通信 大批量数据的传递: 高级通信

Page 4: 第六讲  UNIX 进程通信

1 1 基本概念基本概念2 .基本的通信方式

( a )主从式通信: 通信的双方存在一种隶属关系 , 其中主进程是通信过程的控制者 , 而从进程是通信过程的从属者。主从式通信具有如下特点:在通信过程中主进程对从进程的资源和数据享有使用权限,而从进程对主进程则没有这种权限。在通信过程中汉族要进程始终控制着从进程的工作和动作过程一旦进程的主从关系确定,在正个通信过程中他们的隶属关系不再再发生变化。 例如:终端控制进程和终端进程

Page 5: 第六讲  UNIX 进程通信

1 1 基本概念基本概念2 .基本的通信方式

( b )会话式通信: 通信进程双方采用请求应答的方式进行通信。在会话式通信中,通信的进程双方分为使用者进程个服务者进程。,而使用者进程通过调用服务者进程来完成进程间的通信。 会话式通信的特点:通信时使用者进程需要事先得到服务者进程的允许,方能使用服务者进程为其提供的服务。服务者进程每次都是根据使用者进程提出的请求服务的 ,并且在进程通信的过程中控制权始终为服务者进程所有同样进程间在确定使用会话方式 进行通信时也要建立固定的逻辑关联关系。例如:用户进程与磁盘管理进程 采用 TCP/IP 协议的网间进程通信

使用者进程 服务者进程 request

response

Page 6: 第六讲  UNIX 进程通信

1 1 基本概念基本概念2 .基本的通信方式

( c )消息或邮件通信: 通信双方处于一个平等地位,在通信过程中无论接收进程是否准备好,发送进程都可以进行消息发送。发的消息通过消息系统或邮件系统进行大批量数据传递。消息或邮件通信的特点: 通信中发送进程能否发送消息,只与消息缓冲区或邮箱中是否有足够大的空闲空间来满足这次通信有关,与需要将信息发送到的目的进程的状态无关 发送进程和接收进程之间不需要建立直接的逻辑关联关系 发送信息和接收信息必须通过消息缓冲区或邮箱来完成消息的传递。

消息缓冲或邮箱

发送进程 接收进程

Page 7: 第六讲  UNIX 进程通信

1 1 基本概念基本概念2 .基本的通信方式

( d )共享存储区通信:进程之间采用信息共享存储区的通信方式来完成进程间的通信。信息共享区域是通信进程都可以访问的数据区。共享存储区的进程通信过程的特点:进程通信中,通信的数据或信息不发生存储移动当需要交互时,通信进程双方通过一个共享存储区完成信息交互共享存储中的数据,可以作为需要交互进程的一部分存储在进程体中

共享存储区进程 1

进程 2

Page 8: 第六讲  UNIX 进程通信

2 UNIX2 UNIX 系统进程通信方式 系统进程通信方式 基本通信:早期的 UNIX 系统采用,简单的信息传递,协调进程之间的

同步和互斥。 管道通信:大批量的数据传送,有名管道和无名管道。 IPC :采用消息方式进行进程间通信。

Page 9: 第六讲  UNIX 进程通信

2 UNIX2 UNIX 系统进程通信方式 系统进程通信方式 1 .基本通信(1)锁文件通信: 通信双方在某个指定目录中查找是否有个双方约定的文件存在,并以此来决定通信的动作以及对应的逻辑。进程用对“锁文件创建与否”状态的判定和设置完成一个进程到另一个进程之间的通信。(2) 记录锁定文件通信 即是通过对记录(文件中连续的字节组成特定的数据)的锁定来实现进程通信。(3) 信号 使用软中断信号的通信方式。信号可以做预先的说明,用户使用时只需要根据信号的约定来完成进程之间的通信。

Page 10: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 基本概念

信号是进程中异步发生事件时发出的提示信息或传送给进程的一种事件通知,是 UNIX操作系统用来通知进程发生了某种事件的一种手段。

信号可以由用户进程或核心进程发出,提请系统立即将当前进程中已发生的时间想相关进程进行通告。信号提供了一种处理异步事件的方法。

信号也可以用于进程之间进行通信和实现进程同步处理

Page 11: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 很多种情况会产生信号

当用户在终端按下某些键时,产生终端生成的信号,例如:按下 Delete 键或者 Ctrl-c通常产生一个中断信号 SIGINT ,这是停止正在运行的程序的一种常用手段硬件例外会产生信号,例如:零作为除数、非法存储器访问等。这种情况通常是由硬件而不是 UNIX 内核检测到的,但由内核向发生此错误的哪个进程发送相应的信号,比如发生非法存储器访问时,信号 SIGSEGV将被内核发送到执行了该非法访问的进程。

如果发生了某种必须让进程知道的情况时也会生成信号。这里的情况不是硬件产生的,而是软条件,例如:当进程设置的定时器到期时将生成 SIGALRM信号当进程向一个管道写数据,而此管道已不存在读数据方时,生成 SIGPIPE 信号

某些系统调用将产生信号,例如kill 函数将允许进程发送任何信号给本进程,其他的进程或者进程组raise 函数能够发送任何一个信号给调用它的进程

Page 12: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 生成信号的事件可以归并为三大类1. 程序错误 例如零作除数、非法存储器访问等2. 外部事件 例如用户按下 Delete键、定时器到期等3. 显式请求 进程主动调用 kill函数或者 raise函数

Page 13: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 同步信号和异步信号信号的生成可以是同步的,也可以是异步的。

同步信号与程序中的某个具体操作相关并且在那个操作进行时同时产生。 多数程序错误生成的信号是同步的,例如除零错或内存访问错 由进程显示请求而生成的给自己的信号也是同步的,例如 raise 系统调用

异步信号是接收该信号的进程控制之外的事件生成的信号 一般外部事件总是异步的生成信号 作用于其他进程的 kill 系统调用也异步地生成信号 异步信号可以在进程运行任意时刻产生,进程无法预期信号到达的时刻

Page 14: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 对信号的处理无论是同步还是异步信号,信号发生时,系统对信号可以采用 3种处理方式忽略信号 大部分信号都可以被忽略,只有 2 个除外 SIGSTOP 和SIGKILL 。这两个信号是为了给 root 用户提供杀掉或停止任何进程的一种手段。调用默认动作 系统为每种信号规定了一个默认动作,如果用户进程没有为某个信号设置句柄,则该信号到达时,对该信号的处理由 UNIX内核来完成。通常的默认动作有 : core dump, 终止进程,忽略信号,进程挂起等几种。

捕获信号 需要告诉 UNIX 系统内核,当该信号出现时,调用专门提供的一个函数,类似于 MFC 中的事件函数的概念。这个函数称为信号句柄,或者简称为句柄,它专门对产生信号的事件作出处理。系统调用 signal 为特定信号设置信号句柄

Page 15: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 UNIX 系统为每一种可能的事件定义了一组信号,每一个信号有一个信号名,名字均以 SIG打头, UNIX 系统中常用的信号

信号 描述 默认处理SIGABRT 进程异常中止,调用 abort 函数生成该信号 异常中止SIGALRM 实时闹钟 进程退出SIGFPE 算术例外 异常中止SIGHUP 挂起进程 进程退出SIGILL 非法硬件指令 异常终止SIGKILL 终止进程 进程退出SIGPIPE 写没有读者的管道 进程退出

Page 16: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 UNIX 系统中常用的信号

信号 描述 默认处理SIGQUIT 终端 quit 字符 ( ctrl-\) 异常终止SIGSEGV 非法存储访问(地址越界) 异常终止SIGTERM 终止进程 退出SIGTRAP 硬件自陷 异常终止SIGUSR1 用户自定义信号 退出SIGUSR2 用户自定义信号 退出SIGSTOP 停止进程 退出

可以通过 man –s5 signal 命令或文件 <sys/signal.h> 查看 UNIX 中描述的信号 说明

Page 17: 第六讲  UNIX 进程通信

3 3 信号处理信号处理信号的生成

信号的产生,可以使用三个系统调用int raise( int sig ); 功能 : 给进程自己发送一个信号 参数: sig: 信号标识

Page 18: 第六讲  UNIX 进程通信

3 3 信号处理信号处理信号的生成

int kill( pid_t pid, int sig ); 功能 : 发送一个信号给进程或者进程组 参数: pid: 进程或进程组 id sig: 信号标识pid>0 将信号传给进程识别码为 pid 的进程pid=0 将信号传给和目前进程相同进程组的所有进程pid=-1 将信号广播传送给系统内所有的进程pid<0 将信号传给进程组识别码为 pid绝对值的所有进程

Page 19: 第六讲  UNIX 进程通信

3 3 信号处理信号处理信号的生成

unisigned int alarm( unsigned int seconds ); 功能 : 在 seconds秒后向自己发送一个 SIGALRM 信号 .

Page 20: 第六讲  UNIX 进程通信

3 3 信号处理信号处理kill 函数的实例#include<unistd.h>#include<signal.h>#include<sys/types.h>#include<sys/wait.h>main(){pid_t pid;int status;if(!(pid=fork())){printf("Hi I am child process!\n");sleep(100);return;}

Page 21: 第六讲  UNIX 进程通信

3 3 信号处理信号处理else{printf("send signal to child process (%d)\n",pid);sleep(1);kill(pid ,SIGKILL);wait(&status);if(WIFSIGNALED(status))printf("chile process receive signal %d",WTERMSIG(status));}}

Page 22: 第六讲  UNIX 进程通信

3 3 信号处理信号处理子进程的结束状态返回后存于 status,底下有几个宏可判别结束情况WIFEXITED(status):如果子进程正常结束则为非 0值。WEXITSTATUS(status): 取得子进程 exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏。WIFSIGNALED(status):如果子进程是因为信号而结束则此宏值为真WTERMSIG(status): 取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏。WIFSTOPPED(status):如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。WSTOPSIG(status): 取得引发子进程暂停的信号代码,

Page 23: 第六讲  UNIX 进程通信

3 3 信号处理信号处理

Page 24: 第六讲  UNIX 进程通信

3 3 信号处理信号处理 alarm函数的实例 #include<unistd.h>#include<signal.h>void handler() {printf(“hello\n”);}main(){int i;signal(SIGALRM,handler);alarm(5);for(i=1;i<7;i++){printf(“sleep %d ...\n”,i);sleep(1);}}

Page 25: 第六讲  UNIX 进程通信

3 3 信号处理信号处理运行结果

Page 26: 第六讲  UNIX 进程通信

3 3 信号处理信号处理kill 命令 :kill的语法格式大致有以下两种方式: kill [-s 信号 | -p ] [ -a ] 进程号 ... kill -l [信号 ] -s 指定需要送出的信号。既可以是信号名也可以对应数字。 -p 指定 kill 命令只是显示进程的 pid,并不真正送出结束信号。 -l 显示信号名称列表,这也可以在 /usr/include/linux/signal.h文件中找到。

Page 27: 第六讲  UNIX 进程通信

3 3 信号处理信号处理运行结果

Page 28: 第六讲  UNIX 进程通信

3 3 信号处理信号处理信号的捕获和处理一般情况下,进程捕获到一个信号后,其默认的操作是终止进程。就如同在进程的运行中临时加入了 exit 系统调用,对于该进程的执行情况其父进程可以从该竟的返回代码中了解到。但在有些情况下,进程不允许随意被打断。因此对执行中的进程进行信号捕获和社顶特殊的处理是非常必要的。UNIX 中使用系统调用 signal 接收一种指定类型的信号,并对这种信号做特殊的处理。

Page 29: 第六讲  UNIX 进程通信

3 3 信号处理信号处理系统调用 signal函数void* signal(int sig, void(* func)(int) ) ;

参数 sig 是个整数,指明该系统调用处理哪一个信号参数 func 指明信号 sig 发生时,系统可以采取的 3 种动作之一:常数 SIG_IGN 表示进程运行中接收到指定信号时,采用忽略方式常数 SIG_DFL 恢复对信号的默认处理。也就是说,使用系统自带的处理程序完成信号的处理动作,而不是使用指定函数完成接收信号的处理动作。信号句柄地址 用户自定义的信号处理函数,信号发生时,系统将调用该函数进行处理

Page 30: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 void* signal(int sig, void(* func)(int) )函数的注意事项

由 signal函数建立的信号句柄应当是一个仅有一个整形参数且没有返回值的函数,其整形参数指明该生成的信号

signal函数的返回值是指向信号 sig 的前一次有效动作的指针,当该函数调用成功,将返回 SIG_DFL、 SIG_IGN ,或者信号句柄地址

当信号发生时,如果 func 指向信号句柄,系统在把控制转到信号句柄之前,将首先改变该信号的动作为 SIG_DFL

如果 signal 调用出错,它返回 SIG_ERR ,唯一的错误码是 EINVAL ,即是 sig给出的信号数非法

Page 31: 第六讲  UNIX 进程通信

#include <signal.h>void catch_sig_int( int signo ){ /* signal( SIGINT, catch_sig_int );*/ printf(“SIGINT was caught,user pressed key[delete]\n”); /* signal( SIGINT, catch_sig_int );*/}void main(){ int i = 0; signal( SIGINT, catch_sig_int ); for( i=0; i<5; i++ ) { printf(“ Sleep called #%d\n”, i ); sleep(1); } printf(“exiting..\n”); }

实例1:

Page 32: 第六讲  UNIX 进程通信

3 3 信号处理信号处理运行结果

Page 33: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 实例: sig.c#include <signal.h>void signal_handle(int the_signal); void main( void ){ printf(“the process id is %d\n”, getpid()); if ( signal(SIGUSR1, signal_handle ) == SIG_ERR ) err_exit(“can not catch SIGUSR1\n”); if ( signal(SIGUSR2, signal_handle ) == SIG_ERR ) err_exit(“can not catch SIGUSR2\n”); for( ; ; )   pause(); // pause()函数等待信号的到达}

Page 34: 第六讲  UNIX 进程通信

3 3 信号处理 信号处理 void signal_handle(int the_signal){ if ( the_signal == SIGUSR1 ) printf(“the signal is SIGUSR1”); else if ( the_signal == SIGUSR2) printf(“the signal is SIGUSR2”); else printf(“the received signal is %d\n”, the_signal);

}

Page 35: 第六讲  UNIX 进程通信

%a.out&

the process id is 7346

%

3 3 信号处理 信号处理 执行上面源代码编译后的程序,并调度到后台运行

kill -s SIGUSR1 7346 发送信号 SIGUSR1给进程号为 7346 的进程the signal is SIGUSR1

% 后台进程 7346 收到 SIGUSR1 信号并调用信号句柄kill -s SIGUSR2 7346

发送信号 SIGUSR2给进程号为 7346 的进程the signal is SIGUSR2

% 后台进程 7346 收到 SIGUSR2 信号并调用信号句柄

kill 7346

发送信号 SIGTERM给进程号为 7346 的进程 ,表示要结束该进程

[1]+Terminated a.out&

% 后台进程 7346 收到 SIGTERM 信号 ,调用默认信号动作,该动作终止进程

Page 36: 第六讲  UNIX 进程通信

3 3 信号处理信号处理信号操作:有时候我们希望进程正确的执行 , 而不想进程受到信号的影响 ,比如我们希望上面那个程序在 1秒钟之后不结束 . 这个时候我们就要进行信号的操作 .

信号操作最常用的方法是信号屏蔽,即是进程屏蔽掉某些指定的信号 . 信号屏蔽要用到信号集的概念和几个重要的函数 .

信号集 该数据结构可以表示系统支持的每一个信号 .

在 signal.h 中定义了 sigset_t 来描述信号集

Page 37: 第六讲  UNIX 进程通信

3 3 信号处理信号处理主要的信号操作函数 int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set,int signo);

int sigdelset(sigset_t *set,int signo);

int sigismember(sigset_t *set,int signo);

Page 38: 第六讲  UNIX 进程通信

3 3 信号处理信号处理主要的信号操作函数 int sigprocmask(int how,const sigset_t *set,sigset_t *oset);

功能:改变目前的信号遮罩,其操作依参数 how来决定SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数 set 指定的信号遮罩作联集SIG_UNBLOCK 将目前的信号遮罩删除掉参数 set指定的信号遮罩SIG_SETMASK 将目前的信号遮罩设成参数 set指定的信号遮罩。如果参数 oldset不是 NULL指针,那么目前的信号遮罩会由此指针返回。

!! 注意:信号操作函数 不能对信号 SIGKILL 和 SIGSTOP 进行阻塞屏蔽

Page 39: 第六讲  UNIX 进程通信

3 3 信号处理信号处理以一个实例来解释使用这几个函数 . int main() { sigset_t intmask; sigemptyset( &intmask ); // 将信号集合初始化为空 sigaddset( &intmask, SIGINT ); // 加入中断 Ctrl+C 信号 sigaddset( &intmask, SIGTSTP ); // 加入 SIGTSTP 信号 sigaddset( &intmask, SIGALRM ); // 加入 SIGTSTP 信号 sigaddset( &intmask, SIGTERM ); // 加入 SIGTERM 信号 sigprocmask(SIG_BLOCK,&intmask,NULL); //阻塞信号集 intmask 中的所有信号 , 不保存原来信号集所以 oset 为 NULL . . . . . . . .. . . . . . . . /* 进行事务操作,期间进程不会被用户中断,保证事务处理的完成 */ sigprocmask(SIG_UNBLOCK,&intmask,NULL); //放开上面阻塞的信号集 return;}

Page 40: 第六讲  UNIX 进程通信

3 3 信号处理信号处理等待信号:如果程序是由外部事件所驱动的,或者使用信号进行同步,则需要等待信号的到达 .

UNIX系统提供函数 pause() 和 sigsuspend() 来等待信号int pause(void); 功能:在新的信号到来之前,暂时停止程序的运行int sigsuspend( sigset_t *sigmask); 功能: sigsuspend 函数可以暂时将当前阻塞信号集改变为由 sigmask指定的信号集。改变后 ,sigsuspend会等待 ,直到一个信号被交付。一旦一个信号被交付后 ,原先的信号集被恢复。由于 sigsuspend调用在信号交付之后总是被终止 , 它的返回值总是 -1,errno 总是 EINTR

Page 41: 第六讲  UNIX 进程通信

3 3 信号处理信号处理以一个实例来解释使用这个函数 . int main() { . . . . . . // 功能代码 sigset_t mask; sigset_t old_mask sigemptyset(&mask); // 将信号集合初始化为空 sigaddset(&mask, SIGUSR1); // 加入 SIGUSR1 信号 sigprocmask(SIG_BLOCK,&mask, &old_mask); //获取系统当前信号屏蔽 sigsuspend( &old_mask);// 等待信号的到达,且是当前信号屏蔽集外的信号 . . . . . . . .. . . . . . . . sigprocmask(SIG_SETMASK ,&old_mask, NULL ); //恢复系统原来的信号屏蔽 . . . . . . . . return;}

Page 42: 第六讲  UNIX 进程通信

4 4 管 道 通 信 管 道 通 信 管道( pipe )是 UNIX 中最古老的进程间通信工具,它提供进程之间单向通信的方法。简单说,管道是连接一个进程的输出到另一个进程的输入的一种方法

简介

管道( pipe )的使用很广泛,最常见是在命令行中%cat file | grep ‘pipe’ | more

Page 43: 第六讲  UNIX 进程通信

4 4 管 道 通 信 管 道 通 信

file cat

grep

more

终端

管道

Page 44: 第六讲  UNIX 进程通信

4 4 管 道 通 信 管 道 通 信 UNIX 中的管道用于进程通信,是一种先进先出( FIFO )的特殊文件,,通常是一个进程向管道中写入数据,另一个进程从管道中读出数据,从而完成通信的目的。

实现原理

管道的特点 管道单独构成一种特殊的文件。 管道中,写入的内容每次都添加在管道的末尾,并且每次都是从管道的头部读出数据,就像队列

Page 45: 第六讲  UNIX 进程通信

4 4 管 道 通 信 管 道 通 信 不同点是:对管道写入时,每次 write 调用的结果总是附加在管道的末端,而文件的写入不遵守这个规定,它可以通过指针随意移动对管道写入时,每次写入的字节数不能超过系统常量 PIPE_BUF ,而对文件写入则没有这种规则

( a )管道写和一般文件写 (write)

相同点是: 当设备处于忙状态时, write 调用将被阻塞并被延迟执行 当 write 调用完成时,都能返回实际写入的字节数

Page 46: 第六讲  UNIX 进程通信

4 4 管 道 通 信 管 道 通 信 对管道读时,所有的 read操作总是从管道的当前位置开始,即是管道文件不支持指针的移动,而文件的读不遵守这个规定,它可以通过指针随意移动

( b )管道读和一般文件读 (read)

当管道中没有信息时,对管道进行 read操作将被阻塞,直到有数据才返回;而对空文件进行 read操作,可以返回空串,并不发生阻塞

Page 47: 第六讲  UNIX 进程通信

4.1 4.1 无 名 管 道 无 名 管 道 为了创建无名管道,需要调用 pipe函数 ,pipe函数建立一个管道,使得两个进程可经由它相互传递信息。我们可以将管道视为一块空间,进程可以经由两个不同的文件描述符来分享这块空间;同时 pipe函数产生两个文件描述字,进程通过使用这两个文件描述字来存取管道信息

1 创建无名管道

#include <unistd.h>int pipe( int fdes[2] );

pipe函数的唯一参数是一个由两个整数组成的数组,该函数调用成功后将含有作为管道使用的两个文字描述符,一个作为管道的输入,另一个作为管道的输出。

Page 48: 第六讲  UNIX 进程通信

4.1 4.1 无 名 管 道 无 名 管 道 2 管道读写操作

读操作 int read( int fd, char *buf, int len );写操作 int write( int fd, char *buf, int len );

Page 49: 第六讲  UNIX 进程通信

4.1 4.1 无 名 管 道无 名 管 道 用 pipe函数创建的管道没有名字,是为了一次使用而创建的

3 管道操作特点

管道的两个描述字 fdes[0] 和 fdes[1] 是同时打开的,如果从一个没有任何进程写入的管道读, read 将返回 EOF (文件结束)。如果向一个没有任何进程读取的管道写数据,将产生 SIGPIPE 信号 对有效的管道进行 write操作时,如果管道已满, write函数将被阻塞,直到有数据被读出。 对有效的管道进行 read操作时,如果管道内没有数据, read函数将被阻塞,直到有新数据到达为止。 管道内的数据只能被读出 1 次 管道不允许进行文件的定位操作,读和写操作都是顺序的

Page 50: 第六讲  UNIX 进程通信

4.1 4.1 无 名 管 道无 名 管 道4 例程 :最简单的管道通信void main(){ int fd[2], pid; char msgsend[] = “Hi! Kid .\n”; char msgrecv[32]; if( pipe( fd ) == -1 ) exit(1); if( (pid=fork()) == 0 ) { //pid ==0 子进程 close( fd[1] ); printf(“before read data from pipe!\n”); read( fd[0], msgrecv, strlen(msgsend)); printf(“read [%s] from pipe\n”, msgrecv); }

Page 51: 第六讲  UNIX 进程通信

4.1 4.1 无 名 管 道无 名 管 道 else //父进程 { close( fd[0] ); printf(“Parent sleeping ......”); sleep(3); // 迫使子进程先执行 printf(“Parent wake up !\n”); write( fd[1], msgsend, strlen(msgsend) ); wait(); } exit(0);}

Page 52: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道 基本上,无名管道只能在父子进程之间进行通信,两个没有父子关系的进程,将不能使用这种方式进行通信;因此在无名管道的基础上,UNIX 系统引入了有名管道的方式

Page 53: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道1. 使用命令创建有名管道UNIX 系统中有两个命令可以创建有名管道%mknod myfifo p

或者%mknod a=rw myfifo

%ls –l myfifo

prw-rw-rw- 1 lisi user 0 Nov 27 18:30 myfifo

p表示该文件为有名管道文件

Page 54: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道2. 使用命令操作有名管道有名管道建立后,可以使用一些普通的命令对它进行操作,如果一般的文件操作一样%cat cfile.c > myfifo &

将文件 cfile.c 中的内容传递到有名管道 myfifo 中% cat < myfifo

将有名管道 myfifo 中的内容读出,并显示在标准输出上

Page 55: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道3. 使用函数创建有名管道#include <sys/types.h>#include <sys/stat.h>int mkfifo( char *path, mode_t mode );int mknod( char *path, mode_t mode, dev_t dev );

int mkfifo( “/home/user1/myfifo”, 0666 );

int mknod( “/home/user1/myfifo”, 0666|S_IFIFO, 0);

对有名管道进行读写操作前必须使用 open函数打开管道文件

Page 56: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道其他相关函数int open(const char* filename,int flags[,mode_t mode]);

open函数不能以 O_RDWR 方式打开 open函数可以指定 O_NONBLOCK非阻塞标志int read( int fd, char *buf, int len );

int write( int fd, char *buf, int len );

int close (int fd);

Page 57: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道4. 有名管道和无名管道的区别

特性 有名管道 无名管道进程的使用资格 没有限制 必须有父子关系文件名称 有文件名,可以用 ls –l查看,其文件属性为 p 没有文件名称数据读写的次序 先进先出 先进先出建立的方式 使用mkfifo或者mknod函数 使用 pipe 函数删除的方式 使用 rm命令或者 unlink 函数 被使用完后,会被自动删除

Page 58: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道A.客户端进程完成的工作 1. 建立私有有名管道 2.打开服务端的共有有名管道 3. 等待用户输入命令 4. 将私有管道名和命令写入公有管道 5. 从私有管道中读服务端返回的结果

5. 例程 :一个基于有名管道的 C/S模式应用,客户端发送命令到服务器,服务器执行命令,并将命令返回信息回传给客户端

B. 服务端进程完成的工作 1. 建立服务端公有有名管道 2. 从公有有名管道中读取客户数据 3.执行命令,并将结果写入客户私有管道

Page 59: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道#include <stdio.h>FILE * popen(char *command, char *mode);该函数类似于 system函数,它调用 command参数指定的命令,并返回指向命令执行结果信息的文件句柄。

跟例程相关的两个函数

#include <stdio.h>FILE * pclose(FILE *fp);该函数关闭由 popen函数返回的文件句柄

Page 60: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道//客户端代码#define PUBLIC "/tmp/public"struct message { char fifo_name[256]; char cmd_line[4096];};void main(){ int n,privatefifo, publicfifo; char buffer[4096]; struct message msg; sprintf(msg.fifo_name, "/tmp/fifo%d", getpid()); if( mknod(msg.fifo_name, S_IFIFO|0666, 0) < 0 ) 错误处理 // 建立自己的私有管道 ! publicfifo = open(PUBLIC,O_WRONLY); if( publicfifo == -1 ) // 打开公共管道准备写入 ! 错误处理

Page 61: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道 while(1) { memset(msg.cmd_line, 0, 4096); gets(msg.cmd_line); if( strcmp(msg.cmd_line,"quit") == 0 ) break; write(publicfifo, (char*)msg, sizeof(msg)); // 打开私有管道读 privatefifo = open(msg.fifo_name, O_RDONLY); if( privatefifo == -1) 错误处理 read(privatefifo, buffer, 4096); close(privatefifo); } close(publicfifo); unlink(msg.fifo_name); //删除私有管道文件 !! }

Page 62: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道// 服务端程序#define PUBLIC "/tmp/public"struct message { char fifo_name[256]; char cmd_line[4096];};

void main(){ int n, publicfifo, privatefifo; struct message msg; FILE *fin; char buffer[4096]; mknod(PUBLIC, S_IIFO | 0666, 0 ); // 建立公共管道 !!

publicfifo = open(PUBLIC, O_RDONLY); if ( publicfifo == -1 ) 错误处理

Page 63: 第六讲  UNIX 进程通信

4.2 4.2 有 名 管 道有 名 管 道while( read(publicfifo, (char*)&msg, sizeof(msg)) >0 ) { // 打开客户端建立的私有管道并准备写入数据 privatefifo = open(msg.fifo_name, O_WRONLY | O_NDELAY ); if( privatefifo == -1 ) 错误处理 fin = popen(msg.cmd_line, “r”); //执行命令 while( n = read(fileno(fin), buffer, 4096)) > 0 ) write(privatefifo, buffer, n); // 将从 fin读到的数据写入客户的私有管道 memset(buffer, 0, 4096); pclose(fin); } }

Page 64: 第六讲  UNIX 进程通信

5 IPC5 IPC 在 IPC (Interprocess communication) 的通信模式通常有三种:消息队列、信号量、共享内存。

消息队列方式:使用一个消息结构完成进程间分类格式化数据的传送共享存储区方式:允许进程间共享 虚地址空间的某些区域,以达到信息传送的效果信号量方式:允许进程在一组信号量上进行交互,完成同步地执行

UNIX 系统中,可以使用 ipcs 命令得到当前系统 IPC 的所有信息

Page 65: 第六讲  UNIX 进程通信

5 IPC5 IPC 在 IPC 的通信模式下,每个 IPC 对象有一个唯一的名字,称之为“键”( key )即关键字 , 类似于文件的文件描述符。 在 IPC 的通信模式下, key 的使用使得一个 IPC 对象为多个进程所共用。不使用 key ,进程无法存取对象。 IPC 对象一旦被建立之后,即是全局的。

Page 66: 第六讲  UNIX 进程通信

5 5 信号和消息的比较信号和消息的比较上面我们探讨过信号的使用,下面介绍消息的运用,先将他们进行一下比较

特性 信号 消息数据内容 只是一些预设好的代码用以表示系统发生的某些状况 为一组连续语句或者符号,通常量不会太大 ( 比如 1024字节 )用途 担任进程间少量信息的传送,多半是核心程序用来通知用户进程一些异常的状况

用于进程之间彼此进行数据交换发送时刻 任何时间 不是任何时刻都可以发送发送者辨识 不知道发送者是谁 明确知道发送者是谁送往对象 某个进程 消息队列处理方法 可以处理或者不予理会 必须要处理数据传输效率 不适合较大量的信息传输,效率差 对中等数量的数据传送效率好

Page 67: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列 消息队列 消息队列使进程能将格式化的数据送往任意的进程。消息队列主要由三部分组成:消息队列表、消息头、消息文本。 其中消息队列表中每个队列项包含有该队列的头尾指针,描述了一个队列的位置。 消息头表中包含所有消息队列索引等信息。消息头有指向消息正文的指针。 消息正文指存放真正消息的数据区,即信息的实体。

队列 1队列 2队列 3队列 n

message10 message11 message1m

消息正文 消息正文 消息正文

Page 68: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列 消息队列 1 消息队列的创建或获取

include <sys/types.h>include <sys/ipc.h>include <sys/msg.h>int msgget(key_t key,int msgflg);

消息队列的创建或获取需要用到msgget 函数

参数 Key:关键字值。参数Msgflg: 打开和存取操作权限设置 IPC_CREAT :如果内核中没有此队列,则创建它。 IPC_EXCL和 IPC_CREAT 一起使用时,如果队列已经存在,则失败

Page 69: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列消息队列1.消息队列的创建示例

int msgid;

key_t key=56789;

msgid = msgget( key, 0666 );

//打开一个关键字为 56789 的消息队列,如果该队列已经存在,则函数返回该消息队列的 id ,如果该队列不存在,则函数返回错

int msgid;

key_t key=56789;

msgid = msgget( key, IPC_CREAT|IPC_EXCL|0666 );

// 创建一个关键字为 56789 的消息队列,并设置权限为 0666 ,即是所有用户都能读和写该消息队列,如果该队列已经存在,则函数返回错误 -1

int msgid;

key_t key=56789;

msgid = msgget( key, IPC_CREAT|0666 );

// 创建一个关键字为 56789 的消息队列,并设置权限为 0666 ,即是所有用户都能读和写该消息队列,如果该队列已经存在,则直接得到其队列的标识 id

Page 70: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列 消息队列 2.消息发送

include <sys/types.h>include <sys/ipc.h>include <sys/msg.h>int msgsnd(int msgid,const void *msgp , size_t msgsz int msgflg);

消息发送需要用到函数msgsnd

返回值:如果成功, 0;如果失败, -1参数msgid是消息队列标识符,它是由系统调用msgget 返回的。参数msgp,是指向消息缓冲区的指针。参数msgsz中包含的是消息的字节大小 参数msgflg可以设置为 0(此时为忽略此参数),或者使用 IPC_NOWAIT如果消息队列已满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。如果没有指明,调用进程将会挂起,直到消息可以写入到队列中。

Page 71: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列 消息队列 3.消息接收

include <sys/types.h>include <sys/ipc.h>include <sys/msg.h>Int msgrcv(int msqid,struct msgbuf *msgp,int msgsz,long mtype,int msgflg);

消息接收需要用到函数msgrcv

返回值:如果成功,则返回复制到消息缓冲区的字节数。如果失败,则返回 -1参数msgid用来指定将要读取消息的队列。参数msgp代表要存储消息的消息缓冲区的地址。参数msgsz是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:msgsz=sizeof(structmymsgbuf)-sizeof(long);参数msgflg是要从消息队列中读取的消息的类型。如果此参数的值为 0,那么队列中最长时间的一条消息将返回。如果调用中使用了 IPC_NOWAIT 作为标志,那么当没有数据可以使用时,调用将把 ENOMSG 返回到调用进程中。否则,调用进程将会挂起,直到队列中的一条消息满足 msgrcv() 的参数要求。如果当客户端等待一条消息的时候队列为空,将会返回 EIDRM 。如果进程在等待消息的过程中捕捉到一个信号,则返回 EINTR

Page 72: 第六讲  UNIX 进程通信

5.1 IPC5.1 IPC 消息队列 消息队列 4.消息队列的控制

include <sys/types.h>include <sys/ipc.h>include <sys/msg.h>int msgctl(int msgid,int cmd , struct msqid_ds *buf);

UNIX系统提供 msgctl 函数实现对消息队列的控制

IPC_STAT:读取消息队列的数据结构 msqid_ds ,并将其存储在 buf 指定的地址中。IPC_SET设置消息队列的数据结构 msqid_ds 中的 ipc_perm 元素的值。这个值取自 buf 参数。IPC_RMID从系统内核中移走消息队列。

Page 73: 第六讲  UNIX 进程通信

3. 示例 1有两个程序program1 和 program2,其中 program2 接受用户输入,并将输入内容发送到 program1 , program1显示接收到的消息。 如果用户输入 end,则两个程序都结束// program1.c# include <sys/types.h># include <sys/ipc.h># include <sys/Msg.h>struct my_msg { long mytype; char text[256];};void main(){ int running = 1; int msgid; struct my_msg msgbuf; msgid = msgget( ( key_t )1234, IPC_CREAT|0666 ); if( msgid < 0 ) exit(1);

Page 74: 第六讲  UNIX 进程通信

while( running ) { if( msgrcv( msgid, (void *)&msgbuf, 256, 1, 0) == -1) // 错误处理 printf(“you wrote: %s”, msgbuf.text ); if( strncmp( msgbuf.txt, “end” , 3 ) = = 0 ) running = 0; } if( msgctl( msgid, IPC_RMID, 0 ) == -1 ) // 错误处理 exit( 0 );}

Page 75: 第六讲  UNIX 进程通信

// program2.c# include <sys/types.h># include <sys/ipc.h># include <sys/Msg.h>struct my_msg { long mytype; char text[256];};void main(){ int running = 1; int msgid; struct my_msg msgbuf;

msgid = msgget( ( key_t )1234, IPC_CREAT|0666 ); if( msgid < 0 ) exit(1);

Page 76: 第六讲  UNIX 进程通信

while( running ) { printf(“Enter some text :”); gets( msgbuf.text ); msgbuf.mytype = 1; if( (msgsnd( msgid, (void*)&msgbuf, 256, 0 ) == -1 ) //错误处理 if( strncmp(msgbuf.text, “end”, 3 ) == 0 ) running = 0; } exit( 0 );}

Page 77: 第六讲  UNIX 进程通信

3. 示例 2 使用客户进程和服务器进程,用消息队列机制完成进程间通信。

Page 78: 第六讲  UNIX 进程通信

// 客户端程序 msg_client.c# include <sys/types.h># include <sys/ipc.h># include <sys/Msg.h># define MSGKEY 75struct msgform{ long mtype; char mtext[256];};main(){ struct msgform msg; int msgqid,pid,*pint; msgqid=msgget(MSGKEY,0777); pid=getpid(); printf(“client:pid=%d\n”,pid);

Page 79: 第六讲  UNIX 进程通信

pint=(int*)msg.mtext; *pint=pid; /* 将进程 pid拷贝到缓冲区 */ msg.mtype=1; msgsnd(msgqid,&msg,sizeof(int),0); msgrcv(msgqid,&msg,256,pid,0); printf(“client:receive from pid%d\n”,*pint);}

Page 80: 第六讲  UNIX 进程通信

// 服务端程序 msg_server.c# include <sys/types.h># include <sys/ipc.h># include <sys/Msg.h># define MSGKEY 75struct msgform{ long mtype; char mtext[256];};main(){ struct msgform msg; int msgqid,pid,*pint,i;

extern cleanup(); for(i=0;i<23;i++) signal(i,cleanup);

Page 81: 第六讲  UNIX 进程通信

msgqid=msgget(MSGKEY,0777|IPC_CREAT); printf(“server:pid=%d\n”,getpid()); for(;;) { msgrcv(msgqid,&msg,256,1,0); pint=(int*)msg.mtext; pid=*pint; printf(“server:receive from pid%d\n”,pid); msg.mtype=pid; /* 将接收的客户进程的 pid 为消息类型 */ *pint=getpid(); msgsnd(msgqid,&msg,sizeof(int),0); }}cleanup(){ msgctl(msgqid,IPC_RMID,0); /*删除队列 */

exit();}

Page 82: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 0.信号量和信号的区别信号 (signalsignal) 是核心程序送给用户进程的信息,其目的是通知进程一些突发的事件,和软硬件的状态有关,被处理的优先级高于用户进程。

信号量 (semaphoresemaphore) 用以控制进程之间的同步控制,用来对资源做锁定的工作;使进程间共用一个计数值

Page 83: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 1.基本概念

信号量是一种程序设计方法,它主要用来控制并发的进程之间的互斥情形 和生产 /消费 情形 信号量是 Dijkstra在上世纪 60年代末设计出来的,它的模型模拟的是铁路通行控制信号 在 UNIX 操作系统中,信号量是一个支持两种操作的整型对象,这两种操作即是 P操作和 V操作。

Page 84: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 2.信号量的初始化

在操作一个信号量之前必须调用 semget 函数进行初始化,所谓初始化即是建立 信号量标识信号量标识 以及相关的数据结构。 在 UNIX系统中,所有信号量函数都是对 信号量集信号量集 进行处理,即信号量标识引用的是一组由独立信号组成的信号量集合,这一点类似于信号集 signalset。

Page 85: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 2.信号量的初始化

#include <sys/sem.h>int semget( key_t key, int nsems, int semflg);

semget 函数用来创建一个新的信号量集,或者得到一个已存的信号量集的标识

返回值:如果成功,则返回信号量集的 IPC标识符。如果失败,则返回 -1参数 key是关键字值打开和存取操作与参数 semflg中的内容相关参数 nsems指出了一个新的信号量集中应该创建的信号量的个数

Page 86: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 2.信号量初始化的示例

int semid;

key_t key=56789;

semid = semget( key, 1, IPC_CREAT|IPC_EXCL|0666 );

// 创建一个关键字为 56789 的信号量集,并设置权限为 0666 ,即是所有用户都能读和写该信号量集,信号量集中只有 1 个信号量,如果信号量集已经存在,则函数返回错误

int semid;

key_t key=56789;

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

// 创建一个信号量集,如同刚才的信号量集,关键字为 56789 ,权限为所有用户都能读写,如果该信号量集不存在,则创建之,并返回新建的信号量集的标识,否则就直接返回该已存在的信号量集标识

int semid;

key_t key=56789;

semid = semget( key, 1, 0666);

// 试图打开一个信号量集,如果该信号量集存在,则返回该信号量集的标识,如果该信号量集不存在,则函数返回错

Page 87: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 4.信号量的操作函数 semop 用于操作信号量集合,它的调用接口比较复杂,因为它试图用一种最通用的接口包括各种可能的情形# include <sys/shm.h>int semop(int semid, struct sembuf *sops, size_t nsops);

返回值: 0,如果成功。 -1 ,如果失败第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。

Page 88: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 4.信号量的操作参数 struct sembuf 的详细说明

struct sembuf{ unsigned short int sem_num ; // 信号量个数 short int sem_op; // 信号量操作类型 short int sem_flg; // 信号量操作标志 };

如果 sem_op大于 0, 那么操作将 sem_op 加入到信号量的值中 ,并唤醒等待信号增加的进程 ;如果为 0,当信号量的值是 0的时候 ,函数返回 ,否则阻塞直到信号量的值为 0;如果小于 0, 函数判断信号量的值加上这个负值 .如果结果为 0唤醒等待信号量为 0的进程 ,如果小于 0函数阻塞 .如果大于 0, 那么从信号量里面减去这个值并返回 .

Page 89: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 4.信号量的操作

这三个字段的意义分别为:sem_num:操作信号在信号集中的编号,第一个信号的编号是 0。sem_op:如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如果 sem_op的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于 sem_op的绝对值。通常用于获取资源的使用权;如果 sem_op的值为 0,则操作将暂时阻塞,直到信号的值变为 0。sem_flg:信号操作标志,可能的选择有两种IPC_NOWAIT //对信号的操作不能满足时, semop() 不会阻塞,并立即返回,同时设定错误信息。IPC_UNDO //程序结束时 (不论正常或不正常 ) ,保证信号值会被重设为 semop() 调用前的值。这样做的目的在于避免程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定。

Page 90: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 4.信号量的操作semop 函数的调用需要注意: semop 的调用只有当数组中所有的信号量操作都能成功时,它才成功返回 0 如果数组中有某个信号量操作不能完成,则所有信号量的操作都不会执行,此时调用将返回错或者被阻塞。 被阻塞的 semop 调用能被下面的情况之一恢复:所有信号量的操作都能完成;信号量集合被删除;进程收到一个信号被终止

Page 91: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程

下面的例程说明了信号量的简单用法,该程序可以同时运行多次。这个程序将输出命令行参数,每个参数输出一行,不过程序每输出一个字符,就将 sleep 一,时间为随机数。注意:为了每一行的输出来自同一个进程,必须使用信号量的 PV操作保证进程的互斥访问。// 建立信号量集合的函数#include <sys/sem.h>int open_semaphore( key_t key, int numsems ){ int sid; if( numsems < 1 ) return -1; sid = semget( key, numsems, IPC_CREAT|0666 ); return sid;}

Page 92: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程

// P操作的的函数,企图进入关键区int semaphore_P( int sem_id ){ struct sembuf sbuf; sbuf.sem_num = 0; // 信号量个数 sbuf.sem_op = -1; //减少信号量的值,表示请求资源 sbuf.sem_flg = SEM_UNDO; if( semop( sem_id, &sbuf, 1) == -1 ) { fprintf( stderr, “semaphore P failed\n” ); return 0; } return 1;}

Page 93: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程

// V操作的的函数,离开关键区int semaphore_V( int sem_id ){ struct sembuf sbuf; sbuf.sem_num = 0; // 信号量编号 sbuf.sem_op = 1; // 增加信号量的值,表示释放资源 sbuf.sem_flg = SEM_UNDO; if( semop( sem_id, &sbuf, 1) == -1 ) { fprintf( stderr, “semaphore V failed!\n” ); return 0; } return 1;}

Page 94: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程

// 程序的主函数void main(int argc, char *argv){ int sem_id; // 信号集标识 int i = 0; char *cp; srand( (unsigned int) getpid() ); // 以进程 id 作为随机数种子 sem_id = open_semaphore( (key_t)12345, 1 ); // 创建 /打开信号量集,并获得信号量集的标识}

Page 95: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程 for( i=0; i< argc; i++ ) { cp = argv[i]; // 第 i 个命令行参数 if( semaphore_P( sem_id) == 0 ) exit(1); // 关键区开始 printf(“process %d:”, getpid()); fflush(stdout); while( *cp != NULL ) { putchar( *cp ); fflush(stdout); sleep( rand() % 3 ); //sleep 0 - 2秒 cp++; } // 关键区结束 if( semaphore_V( sem_id) == 0 ) exit(1); } printf(“\n %d – finished\n”, getpid() ); // 关键区外,可能影响其他进程的输出}

Page 96: 第六讲  UNIX 进程通信

5.2 5.2 信 号 量 信 号 量 5.信号量例程 输出结果% a.out 1 test semaphore&[1] 7411% a.out are you success?process 7411:a.outprocess 7411:1process 7432:a.outprocess 7432:areprocess 7432:youprocess 7411:testprocess 7411:semaphoreprocess 7432:su7411 – finishedccess?

7432 – finished[1] + Done a.out 1 test semaphore

Page 97: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 1.基本概念

共享存储是物理存储器中一段可以由 2 个以上的进程共享的存储空间。共享存储段具有大小和物理存储地址,想要访问共享存储段的进程可以连接这段存储区域到自己的地址空间中任何适合的地方,其他进程也一样。这样,多个进程就可以访问相同的物理存储

Page 98: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 2.共享存储的特点 共享存储提供了进程间共享数据的最快途径 共享存储并不在读写数据之间提供任何同步方法

Page 99: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 3.共享存储段的创建

同消息队列一样,需要访问共享存储的进程必须调用 shmget 函数获得共享存储标识,也称为打开 /创建一个共享存储段# include <sys/shm.h>int shmget( key_t key, size_t size, int shmflg);

Page 100: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 3.共享存储段的创建示例

int shmid;

key_t key=56789;

shmid = shmget( key, 1024, IPC_CREAT|0666 );

// 创建一个关键字为 56789 的共享段,并设置权限为 0666 ,即是所有用户都能读和写该共享段,如果该段已经存在,则直接得到其共享段的 id

int shmid;

key_t key=56789;

shmid = shmget( key, 1024, IPC_CREAT|IPC_EXCL|0666 );

// 创建一个关键字为 56789 的共享段,并设置权限为 0666 ,即是所有用户都能读和写该共享段,如果该段已经存在,则函数返回错误

int shmid;

key_t key=56789;

shmid = shmget( key, 0, 0666 );

//打开一个关键字为 56789 的共享段,如果该段已经存在,则函数返回该段的 id ,如果该段不存在,则函数返回错

Page 101: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 4.共享存储段的连接当进程调用 shmget创建或者打开一个共享存储段后,还必须调用 shmat 函数连接这个共享段到自己的地址空间,然后才能读写它# include <sys/shm.h>void * shmat(int shmid, void *shmaddr, int shmflg);

shmid为 shmget 函数返回的共享存储标识符, addr和 flag 参数决定了以什么方式来确定连接的地址返回值:如果成功,则返回共享内存段连接到进程中的地址。如果失败,则返回 - 1

Page 102: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 连接方式: 若 shmaddr等于 (void *)0,则段联接到由系统选择的第一个可用的地址上。 若 shmaddr不等于 (void *)0同时 (shmflg&SHM_RND)值为真 ,则段联接到由 (shmaddr-(shmaddr%SHMLBA))给出的地址上。若 shmaddr不等于 (void *)0同时 (shmflg&SHM_RND)值为假 ,则段联接到由 shmaddr指定的地址上 .若 (shmflg&sSHM_RDONLY) 为真并且调用进程有读允许 ,则被联接的段为只读 ;否则 ,若值不为真且调用进程有读写权限 ,则被联接的段为可读写的 .

Page 103: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 5.共享存储段的分离

当进程不再需要一个共享存储段时,可以使用函数 shmdt将它从进程的地址空间分离 ,当进程退出执行时,系统会自动分离它连接的所有共享存储段# include <sys/shm.h>int shmdt( void * shmaddr );

shmaddr:共享内存段连接到进程中的地址返回值:如果失败,则返回 - 1

Page 104: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 6.简单例程:服务程序和客户程序共享一个存储段,服务端向共享段写入数据,客户端从共享段读出数据并显示

// 服务端程序# include <sys/shm.h>void main(){ char c; int shmid; key_t key = 56789; char *shm, *s; if( (shmid = shmget(key, 27, IPC_CREAT|0666)) < 0 ) 错误处理 . . . . // 建立共享存储段 if( ( shm = shmat( shmid,NULL,0)) < 0 ) 错误处理 . . . . // 连接共享存储段

Page 105: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 s = shm; for( c =‘a’; c < = ‘z’; c++ ) { *s = c; s++; } *s = NULL; //表示存储的字符的结束 while( *shm != ‘*’ ) sleep(1);

exit( 0 );

Page 106: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 客户端程序# include <sys/shm.h>void main(){ int shmid; key_t key = 56789; char *shm, *s; if( (shmid =shmget(key, 0, 0666)) < 0 ) 错误处理 . . . . // 建立共享存储段 if( ( shm =shmat( shmid,NULL,0)) < 0 ) 错误处理 . . . . // 连接共享存储段

Page 107: 第六讲  UNIX 进程通信

5.3 5.3 共 享 存 储 共 享 存 储 for( s=shm; *s!=NULL; s++ ) putchar( *s ); putchar(‘\n’); *shm = ‘*’; // 设置字符,通知服务端 shmdt( shm ); // 分离共享存储段 exit(0); }