《嵌入式linux应用程序开发技术详解 8.pdf》由会员分享,可在线阅读,更多相关《嵌入式linux应用程序开发技术详解 8.pdf(41页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、华清远见嵌入式培训专家 http:/ 华清远见培训教材 “黑色经典”系列之嵌入式“黑色经典”系列之嵌入式 Linux 应用程序开发详解应用程序开发详解 第 8 章 进程间通信 本章目标 在上一章中,读者已经学会了如何创建进程以及如何对进程进行基本的控制,而这些都只是停留在父子进程之间的控制,本章将要学习不同的进程间进行通信的方法,通过本章的学习,读者将会掌握如下内容。掌握 Linux 中管道的基本概念 掌握 Linux 中管道的创建 掌握 Linux 中管道的读写 掌握 Linux 中有名管道的创建读写方法 掌握 Linux 中消息队列的处理 掌握 Linux 共享内存的处理 华清远见嵌入式培
2、训专家 http:/ 华清远见培训教材 8.1 Linux 下进程间通信概述 在上一章中,读者已经知道了进程是一个程序的一次执行的过程。这里所说的进程一般是指运行在用户态的进程,而由于处于用户态的不同进程之间是彼此隔离的,就像处于不同城市的人们,它们必须通过某种方式来提供通信,例如人们现在广泛使用的手机等方式。本章就是讲述如何建立这些不同的通话方式,就像人们有多种通信方式一样。Linux 下的进程通信手段基本上是从 UNIX 平台上的进程通信手段继承而来的。而对UNIX 发展做出重大贡献的两大主力 AT&T 的贝尔实验室及 BSD(加州大学伯克利分校的伯克利软件发布中心)在进程间的通信方面的侧
3、重点有所不同。前者是对 UNIX 早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内;后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。而 Linux则把两者的优势都继承了下来,如图 8.1 所示。UNIX 进程间通信(IPC)方式包括管道、FIFO、信号。最初UNIX 的进程间通信 基于 Socket 进程间通信 基于 System V 进程间通信 POSIX 进程间通信 Linux 进程间通信 图 8.1 进程间通信发展历程 System V 进程间通信(IPC)包括 System V 消息队列、System
4、 V 信号灯、System V共享内存区。Posix 进程间通信(IPC)包括 Posix 消息队列、Posix 信号灯、Posix 共享内存区。现在在 Linux 中使用较多的进程间通信方式主要有以下几种。(1)管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。(2)信号(Signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知接受进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一样的。嵌入式 Linux 应用程序开发详解第 8
5、 章、进程间通信 华清远见培训教材(3)消息队列:消息队列是消息的链接表,包括 Posix 消息队列 systemV 消息队列。它克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读取消息。(4)共享内存:可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。(5)信号量:主要作为进程间以及同一进程不同线程之间的同步手段。(6)套接字(Socket):这是一种更为一般的进程间通信机
6、制,它可用于不同机器之间的进程间通信,应用非常广泛。本章会详细介绍前 4 种进程通信方式,对第 5 种通信方式将会在第 10 章中单独介绍。8.2 管道通信 8.2.1 管道概述 细心的读者可能会注意到本书在第 2 章中介绍“ps”的命令时提到过管道,当时指出了管道是 Linux 中很重要的一种通信方式,它是把一个程序的输出直接连接到另一个程序的输入,这里仍以第 2 章中的“ps ef|grep ntp”为例,描述管道的通信过程,如图 8.2 所示。内核 管道 进程 ps-ef 进程 grep ntp 图 8.2 管道的通信过程 管道是 Linux 中进程间通信的一种方式。这里所说的管道主要指
7、无名管道,它具有如下特点。它只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程之间)。它是一个半双工的通信模式,具有固定的读端和写端。管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的 read、write 等华清远见嵌入式培训专家 http:/ 华清远见培训教材 函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。8.2.2 管道创建与关闭 1管道创建与关闭说明 管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符 fds0和 fds1,其中 fds0固定用于读管道,而 fd1固定用于写管道,如图 8.3 所示,这样就构成了一
8、个半双工的通道。内核 管道 进程 ps-ef 进程 grep ntp 图 8.3 Linux 中管道与文件描述符的关系 管道关闭时只需将这两个文件描述符关闭即可,可使用普通的 close 函数逐个关闭各个文件描述符。注意 一个管道共享了多对文件描述符时,若将其中的一对读写文件描述符都删除,则该管道就失效。2管道创建函数 创建管道可以通过调用 pipe 来实现,下表 8.1 列出了 pipe 函数的语法要点。表 8.1 pipe 函数语法要点 所需头文件#include 函数原型 int pipe(int fd2)函数传入值 fd2:管道的两个文件描述符,之后就可以直接操作这两个文件描述符 成功
9、:0 函数返回值 出错:1 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 3管道创建实例 创建管道非常简单,只需调用函数 pipe 即可,如下所示:/*pipe.c*/#include#include#include#include int main()int pipe_fd2;/*创建一无名管道*/if(pipe(pipe_fd)0)printf(pipe create errorn);return 1;else printf(pipe create successn);/*关闭管道描述符*/close(pipe_fd0);close(pipe_fd1);程序
10、运行后先成功创建一个无名管道,之后再将其关闭。8.2.3 管道读写 1管道读写说明 用pipe函数创建的管道两端处于一个进程中,由于管道是主要用于在不同进程间通信的,因此这在实际应用中没有太大意义。实际上,通常先是创建一个管道,再通过 fork()函数创建一子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系就如图 8.4 所示。华清远见嵌入式培训专家 http:/ 华清远见培训教材 父进程 f d0 f d1 子进程 f d0 f d1 内核 管道 图 8.4 父子进程管道的文件描述符对应关系 这时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条
11、件。这时,父子进程分别拥有自己的读写的通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如在图 8.5 中把父进程的写端 fd1和子进程的读端 fd0关闭。这时,父子进程之间就建立起了一条“子进程写入父进程读”的通道。父进程 f d0 f d1 子进程 f d0 f d1内核 管道 图 8.5 关闭父进程 fd1和子进程 fd0 同样,也可以关闭父进程的 fd0和子进程的 fd1,这样就可以建立一条“父进程写,子进程读”的通道。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的 fd0和 fd1,这时,只需要关闭相应端口就可以建立其各子进程之间的通道。想一
12、想 为什么无名管道只能建立具有亲缘关系的进程之间?嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 2管道读写实例 在本例中,首先创建管道,之后父进程使用 fork 函数创建子进程,之后通过关闭父进程的读描述符和子进程的写描述符,建立起它们之间的管道通信。/*pipe_rw.c*/#include#include#include#include#include int main()int pipe_fd2;pid_t pid;char buf_r100;char*p_wbuf;int r_num;memset(buf_r,0,sizeof(buf_r);/*创建管道
13、*/if(pipe(pipe_fd)0)printf(%d numbers read from the pipe is%sn,r_num,buf_r);/*关闭子进程读描述符*/close(pipe_fd0);exit(0);华清远见嵌入式培训专家 http:/ 华清远见培训教材 else if(pid0)/*/关闭父进程读描述符,并分两次向管道中写入 Hello Pipe*/close(pipe_fd0);if(write(pipe_fd1,Hello,5)!=1)printf(parent write1 success!n);if(write(pipe_fd1,Pipe,5)!=1)pri
14、ntf(parent write2 success!n);/*关闭父进程写描述符*/close(pipe_fd1);sleep(3);/*收集子进程退出信息*/waitpid(pid,NULL,0);exit(0);将该程序交叉编译,下载到开发板上的运行结果如下所示:root(none)1#./pipe_rw2 parent write1 success!parent write2 success!10 numbers read from the pipe is Hello Pipe 3管道读写注意点 只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的
15、 SIFPIPE 信号(通常 Broken pipe 错误)。向管道中写入数据时,linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用 sleep 函数。8.2.4 标准流管道 1标准流管道函数说明 与 Linux 中文件操作有基于文件流的标准 I/O 操作一样,管道的操作也支持基于文件流的模式。这种基于文件流的管道主要是用来创建一个连接到另一个进程的管道,这里的“另一个进程”也就是一个可以进行
16、一定操作的可执行文件,例如,用户执行“cat popen.c”或者自己编写的程序“hello”等。由于这一类操作很常用,因此标准流管道就将一系列的创建 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 过程合并到一个函数 popen 中完成。它所完成的工作有以下几步。创建一个管道。fork 一个子进程。在父子进程中关闭不需要的文件描述符。执行 exec 函数族调用。执行函数中所指定的命令。这个函数的使用可以大大减少代码的编写量,但同时也有一些不利之处,例如,它没有前面管道创建的函数灵活多样,并且用 popen 创建的管道必须使用标准 I/O 函数进行操作,但不能使用
17、前面的 read、write 一类不带缓冲的 I/O 函数。与之相对应,关闭用 popen 创建的流管道必须使用函数 pclose 来关闭该管道流。该函数关闭标准 I/O 流,并等待命令执行结束。2函数格式 popen 和 pclose 函数格式如表 8.2 和表 8.3 所示。表 8.2 popen 函数语法要点 所需头文件#include 函数原型 FILE*popen(const char*command,const char*type)Command:指向的是一个以 null 结束符结尾的字符串,这个字符串包含一个 shell命令,并被送到/bin/sh 以-c 参数执行,即由 she
18、ll 来执行 函数传入值 type:“r”:文件指针连接到 command 的标准输出,即该命令的结果产生输出“w”:文件指针连接到 command 的标准输入,即该命令的结果产生输入 成功:文件流指针 函数返回值 出错:1 表 8.3 pclose 函数语法要点 所需头文件#include 函数原型 int pclose(FILE*stream)函数传入值 stream:要关闭的文件流 成功:返回 popen 中执行命令的终止状态 函数返回值 出错:1 3函数使用实例 在该实例中,使用 popen 来执行“ps-ef”命令。可以看出,popen 函数的使用能够使程序变得短小精悍。/*pope
19、n.c*/华清远见嵌入式培训专家 http:/ 华清远见培训教材#include#include#include#include#define BUFSIZE 1000 int main()FILE*fp;char*cmd=ps-ef;char bufBUFSIZE;/*调用 popen 函数执行相应的命令*/if(fp=popen(cmd,r)=NULL)perror(popen);while(fgets(buf,BUFSIZE,fp)!=NULL)printf(%s,buf);pclose(fp);exit(0);下面是该程序在目标板上的执行结果。root(none)1#./popen P
20、ID TTY Uid Size State Command 1 root 1832 S init 2 root 0 S keventd 3 root 0 S ksoftirqd_CPU0 4 root 0 S kswapd 5 root 0 S bdflush 6 root 0 S kupdated 7 root 0 S mtdblockd 8 root 0 S khubd 35 root 2104 S /bin/bash/usr/etc/rc.local 36 root 2324 S /bin/bash 41 root 1364 S /sbin/inetd 53 root 14260 S /
21、Qtopia/qtopia-free-1.7.0/bin/qpe-qws 54 root 11672 S quicklauncher 55 root 0 S usb-storage-0 56 root 0 S scsi_eh_0 74 root 1284 S ./popen 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 75 root 1836 S sh-c ps-ef 76 root 2020 R ps ef 8.2.5 FIFO 1有名管道说明 前面介绍的管道是无名管道,它只能用于具有亲缘关系的进程之间,这就大大地限制了管道的使用。有名管道的出现突破了这种限
22、制,它可以使互不相关的两个进程实现彼此通信。该管道可以通过路径名来指出,并且在文件系统中是可见的。在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。不过值得注意的是,FIFO 是严格地遵循先进先出规则的,对管道及 FIFO 的读总是从开始处返回数据,对它们的写则把数据添加到末尾,它们不支持如 lseek()等文件定位操作。有名管道的创建可以使用函数 mkfifo(),该函数类似文件中的 open()操作,可以指定管道的路径和打开的模式。小知识 用户还可以在命令行使用“mknod 管道名 p”来创建有名管道。在创建管道成功之后,就可以使用 open、read、wri
23、te 这些函数了。与普通文件的开发设置一样,对于为读而打开的管道可在 open 中设置 O_RDONLY,对于为写而打开的管道可在open 中设置 O_WRONLY,在这里与普通文件不同的是阻塞问题。由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在 open 函数中设定为 O_NONBLOCK。下面分别对阻塞打开和非阻塞打开的读写进行一定的讨论。对于读进程 若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞直到有数据写入。若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。对于写进程 若该管道是阻塞
24、打开,则写进程而言将一直阻塞直到有读进程读出数据。若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行读操作。2mkfifo 函数格式 表 8.4 列出了 mkfifo 函数的语法要点。表 8.4 mkfifo 函数语法要点 所需头文件#include#include 函数原型 int mkfifo(const char*filename,mode_t mode)函数传入值 filename:要创建的管道 续表 华清远见嵌入式培训专家 http:/ 华清远见培训教材 O_RDONLY:读管道 O_WRONLY:写管道 O_RDWR:读写管道 O_NONBLOCK:非阻塞 O
25、_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三的参数为其设置权限 函数传入值 mode:O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在 成功:0 函数返回值 出错:1 表 8.5 再对 FIFO 相关的出错信息做一归纳,以方便用户差错。表 8.5 FIFO 相关的出错信息 EACCESS 参数 filename 所指定的目录路径无可执行的权限 EEXIST 参数 filename 所指定的文件已存在 ENAMETOOLONG 参数 filename 的路径名称太长 ENOENT 参数 filename 包含的目录不存在 EN
26、OSPC 文件系统的剩余空间不足 ENOTDIR 参数 filename 路径中的目录存在但却非真正的目录 EROFS 参数 filename 指定的文件存在于只读文件系统内 3使用实例 下面的实例包含了两个程序,一个用于读管道,另一个用于写管道。其中在写管道的程序里创建管道,并且作为 main 函数里的参数由用户输入要写入的内容。读管道读出了用户写入管道的内容,这两个函数用的是非阻塞读写管道。/*fifo_write.c*/#include#include#include#include#include#include#include#define FIFO/tmp/myfifo main(
27、int argc,char*argv)嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材/*参数为即将写入的字节数*/int fd;char w_buf100;int nwrite;if(fd=1)if(errno=ENXIO)printf(open error;no reading processn);/*打开 FIFO 管道,并设置非阻塞标志*/fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);if(argc=1)printf(Please send somethingn);strcpy(w_buf,argv1);/*向管道中写
28、入字符串*/if(nwrite=write(fd,w_buf,100)=1)if(errno=EAGAIN)printf(The FIFO has not been read yet.Please try latern);else printf(write%s to the FIFOn,w_buf);/*fifl_read.c*/#include#include#include#include#include#include#include#define FIFO/tmp/myfifo main(int argc,char*argv)char buf_r100;int fd;int nread
29、;/*创建有名管道,并设置相应的权限*/华清远见嵌入式培训专家 http:/ 华清远见培训教材 if(mkfifo(FIFO,O_CREAT|O_EXCL)0)&(errno!=EEXIST)printf(cannot create fifoservern);printf(Preparing for reading bytes.n);memset(buf_r,0,sizeof(buf_r);/*打开有名管道,并设置非阻塞标志*/fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);if(fd=1)perror(open);exit(1);while(1)memset(buf_
30、r,0,sizeof(buf_r);if(nread=read(fd,buf_r,100)=1)if(errno=EAGAIN)printf(no data yetn);printf(read%s from FIFOn,buf_r);sleep(1);pause();unlink(FIFO);为了能够较好地观察运行结果,需要把这两个程序分别在两个终端里运行,在这里首先启动读管道程序。由于这是非阻塞管道,因此在建立管道之后程序就开始循环从管道里读出内容。在启动了写管道程序后,读进程能够从管道里读出用户的输入内容,程序运行结果如下所示。终端一:rootlocalhost FIFO#./read P
31、reparing for reading bytes read from FIFO read from FIFO read from FIFO read from FIFO read from FIFO read hello from FIFO read from FIFO 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 read from FIFO read FIFO from FIFO read from FIFO read from FIFO 终端二:rootlocalhost#./write hello write hello to the FIFO ro
32、otlocalhost#./read FIFO write FIFO to the FIFO 8.3 信号通信 8.3.1 信号概述 信号是 UNIX 中所使用的进程通信的一种最古老的方法。它是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。它可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。细心的读者是否
33、还记得,在第 2 章 kill 命令中曾讲解到“l”选项,这个选项可以列出该系统所支持的所有信号列表。在笔者的系统中,信号值在 32 之前的则有不同的名称,而信号值在 32 以后的都是用“SIGRTMIN”或“SIGRTMAX”开头的,这就是两类典型的信号。前者是从 UNIX 系统中继承下来的信号,为不可靠信号(也称为非实时信号);后者是为了解决前面“不可靠信号”的问题而进行了更改和扩充的信号,称为“可靠信号”(也称为实时信号)。那么为什么之前的信号不可靠呢?这里首先要介绍一下信号的生命周期。一个完整的信号生命周期可以分为 3 个重要阶段,这 3 个阶段由 4 个重要事件来刻画的:信号产生、信
34、号在进程中注册、信号在进程中注销、执行信号处理函数,如图8.6 所示。相邻两个事件的时间间隔构成信号生命周期的一个阶段。要注意这里的信号处理有多种方式,一般是由内核完成的,当然也可以由用户进程来完成,故在此没有明确画出。一个不可靠信号的处理过程是这样的:如果发现该信号已经在进程中注册,那么就忽略该信号。因此,若前一个信号还未注销又产生了相同的信号就会产生信号丢失。而当可靠信号发送给一个进程时,不管该信号是否已经在进程中注册,都会被再注册一次,因此信号就不会丢失。所有可靠信号都支持排队,而不可靠信号则都不支持排队。华清远见嵌入式培训专家 http:/ 华清远见培训教材 内核进程 信号产生 信号处
35、理 用户进程 信号注册 信号注销 图 8.6 信号生命周期 注意 这里信号的产生、注册、注销等是指信号的内部实现机制,而不是信号的函数实现。因此,信号注册与否,与本节后面讲到的发送信号函数(如 kill()等)以及信号安装函数(如 signal()等)无关,只与信号值有关。用户进程对信号的响应可以有 3 种方式。忽略信号,即对信号不做任何处理,但是有两个信号不能忽略,即 SIGKILL 及SIGSTOP。捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数。执行缺省操作,Linux 对每种信号都规定了默认操作。Linux 中的大多数信号是提供给内核的,表 8.6 列出了 Linux 中
36、最为常见信号的含义及其默认操作。表 8.6 常见信号的含义及其默认操作 信 号 名 含 义 默 认 操 作 SIGHUP 该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联 终止 SIGINT 该信号在用户键入INTR 字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程 终止 SIGQUIT 该信号和 SIGINT 类似,但由 QUIT 字符(通常是 Ctrl-)来控制 终止 SIGILL 该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出
37、终止 SIGFPE 该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为 0 等其他所有的算术的错误 终止 SIGKILL 该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略 终止 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 SIGALRM 该信号当一个定时器到时的时候发出 终止 SIGSTOP 该信号用于暂停一个进程,且不能被阻塞、处理或忽略 暂停进程 续表 信 号 名 含 义 默 认 操 作 SIGTSTP 该信号用于交互停止进程,用户可键入 SUSP 字符时(通常是 Ctrl+Z)发出这个信号 停止进程 SIGCHLD
38、 子进程改变状态时,父进程会收到这个信号 忽略 SIGABORT 8.3.2 信号发送与捕捉 发送信号的函数主要有 kill()、raise()、alarm()以及 pause(),下面就依次对其进行介绍。1kill()和 raise()(1)函数说明 kill 函数同读者熟知的 kill 系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是 kill 函数的一个用户接口)。这里要注意的是,它不仅可以中止进程(实际上发出 SIGKILL 信号),也可以向进程发送其他信号。与 kill 函数所不同的是,raise 函数允许进程向自身发送信号。(2)函数格式 表 8.7 列出了
39、kill 函数的语法要点。表 8.7 kill 函数语法要点 所需头文件#include#include 函数原型 int kill(pid_t pid,int sig)正数:要发送信号的进程号 0:信号被发送到所有和 pid 进程在同一个进程组的进程 pid:1:信号发给所有的进程表中的进程(除了进程号最大的进程外)函数传入值 sig:信号 成功:0 函数返回值 出错:1 表 8.8 列出了 raise 函数的语法要点。表 8.8 raise 函数语法要点 所需头文件#include#include 华清远见嵌入式培训专家 http:/ 华清远见培训教材 函数原型 int raise(int
40、 sig)函数传入值 sig:信号 成功:0 函数返回值 出错:1(3)函数实例 下面这个示例首先使用fork创建了一个子进程,接着为了保证子进程不在父进程调用kill之前退出,在子进程中使用 raise 函数向子进程发送 SIGSTOP 信号,使子进程暂停。接下来再在父进程中调用 kill 向子进程发送信号,在该示例中使用的是 SIGKILL,读者可以使用其他信号进行练习。/*kill.c*/#include#include#include#include#include int main()pid_t pid;int ret;/*创建一子进程*/if(pid=fork()0)perror(
41、fork);exit(1);if(pid=0)/*在子进程中使用 raise 函数发出 SIGSTOP 信号*/raise(SIGSTOP);exit(0);else/*在父进程中收集子进程发出的信号,并调用 kill 函数进行相应的操作*/printf(pid=%dn,pid);if(waitpid(pid,NULL,WNOHANG)=0)if(ret=kill(pid,SIGKILL)=0)printf(kill%dn,pid);else 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 perror(kill);该程序运行结果如下所示:root(none)tm
42、p#./kill pid=78 kill 78 2alarm()和 pause()(1)函数说明 alarm 也称为闹钟函数,它可以在进程中设置一个定时器,当定时器指定的时间到时,它就向进程发送 SIGALARM 信号。要注意的是,一个进程只能有一个闹钟时间,如果在调用 alarm之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。pause 函数是用于将调用进程挂起直至捕捉到信号为止。这个函数很常用,通常可以用于判断信号是否已到。(2)函数格式 表 8.9 列出了 alarm函数的语法要点。表 8.9 alarm 函数语法要点 所需头文件#include 函数原型 unsigned i
43、nt alarm(unsigned int seconds)函数传入值 seconds:指定秒数 成功:如果调用此 alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回 0 函数返回值 出错:1 表 8.10 列出了 pause 函数的语法要点。表 8.10 pause 函数语法要点 所需头文件#include 函数原型 int pause(void)函数返回值 1,并且把 error 值设为 EINTR (3)函数实例 该实例实际上已完成了一个简单的 sleep 函数的功能,由于 SIGALARM 默认的系统动作为终止该进程,因此在程序调用 pause 之后,
44、程序就终止了。如下所示:/*alarm.c*/华清远见嵌入式培训专家 http:/ 华清远见培训教材#include#include#include int main()int ret;/*调用 alarm 定时器函数*/ret=alarm(5);pause();printf(I have been waken up.n,ret);root(none)tmp#./alarm Alarm clock 想一想 用这种形式实现的 sleep 功能有什么问题?8.3.3 信号的处理 在了解了信号的产生与捕获之后,接下来就要对信号进行具体的操作了。从前面的信号概述中读者也可以看到,特定的信号是与一定的进
45、程相联系的。也就是说,一个进程可以决定在该进程中需要对哪些信号进行什么样的处理。例如,一个进程可以选择忽略某些信号而只处理其他一些信号,另外,一个进程还可以选择如何处理信号。总之,这些都是与特定的进程相联系的。因此,首先就要建立其信号与进程之间的对应关系,这就是信号的处理。注意 请读者区分信号的注册与信号的处理之间的差别,前者信号是主动方,而后者进程是主动方。信号的注册是在进程选择了特定信号处理之后特定信号的主动行为。信号处理的主要方法有两种,一种是使用简单的 signal 函数,另一种是使用信号集函数组。下面分别介绍这两种处理方式。1signal()(1)函数说明 使用 signal 函数处
46、理时,只需把要处理的信号和处理函数列出即可。它主要是用于前 32种非实时信号的处理,不支持信号传递信息,但是由于使用简单、易于理解,因此也受到很多程序员的欢迎。(2)函数格式 Signal 函数的语法要点如表 8.11 所示。表 8.11 signal 函数语法要点 嵌入式 Linux 应用程序开发详解第 8 章、进程间通信 华清远见培训教材 所需头文件#include 函数原型 void(*signal(int signum,void(*handler)(int)(int)signum:指定信号 SIG_IGN:忽略该信号 SIG_DFL:采用系统默认方式处理信号 函数传入值 handler
47、:自定义的信号处理函数指针 续表 成功:以前的信号处理配置 函数返回值 出错:1 这里需要对这个函数原型进行说明。这个函数原型非常复杂。可先用如下的 typedef 进行替换说明:typedef void sign(int);sign*signal(int,handler*);可见,首先该函数原型整体指向一个无返回值带一个整型参数的函数指针,也就是信号的原始配置函数。接着该原型又带有两个参数,其中的第二个参数可以是用户自定义的信号处理函数的函数指针。(3)使用实例 该示例表明了如何使用 signal 函数捕捉相应信号,并做出给定的处理。这里,my_func就是信号处理的函数指针。读者还可以将其
48、改为 SIG_IGN 或 SIG_DFL 查看运行结果。/*mysignal.c*/#include#include#include /*自定义信号处理函数*/void my_func(int sign_no)if(sign_no=SIGINT)printf(I have get SIGINTn);else if(sign_no=SIGQUIT)printf(I have get SIGQUITn);int main()printf(Waiting for signal SIGINT or SIGQUIT n);/*发出相应的信号,并跳转到信号处理函数处*/signal(SIGINT,my_f
49、unc);华清远见嵌入式培训专家 http:/ 华清远见培训教材 signal(SIGQUIT,my_func);pause();exit(0);rootwww yul#./mysignal Waiting for signal SIGINT or SIGQUIT I have get SIGINT rootwww yul#./mysignal Waiting for signal SIGINT or SIGQUIT I have get SIGQUIT 2信号集函数组(1)函数说明 使用信号集函数组处理信号时涉及一系列的函数,这些函数按照调用的先后次序可分为以下几大功能模块:创建信号集合、登
50、记信号处理器以及检测信号。其中,创建信号集合主要用于创建用户感兴趣的信号,其函数包括以下几个。sigemptyset:初始化信号集合为空。sigfillset:初始化信号集合为所有信号的集合。sigaddset:将指定信号加入到信号集合中去。sigdelset:将指定信号从信号集中删去。sigismember:查询指定信号是否在信号集合之中。登记信号处理器主要用于决定进程如何处理信号。这里要注意的是,信号集里的信号并不是真正可以处理的信号,只有当信号的状态处于非阻塞状态时才真正起作用。因此,首先就要判断出当前阻塞能不能传递给该信号的信号集。这里首先使用 sigprocmask 函数判断检测或更