进程间通信-IPC_图文

linux进程间通信

linux进程间通信
? 进程间通信IPC(interprocess Communication)提供了一种不同进程间 可以互相访问数据的方式。相互访问的数据不仅包括程序运行时的适 时数据,也包括对对方代码段的访问。
? 进程间通信的目的: 1、数据传输:一个进程需要将它的数据发送给另一个进程,发送的数
据量在一个字节到几兆字节之间。 2、共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,
别的进程应该立刻看到。 3、通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它
们)发生了某种事件(如进程终止时要通知父进程)。 4、资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内
核提供锁和同步机制。 5、进程控制:有些进程希望完全控制另一个进程的执行(如Debug进
程),此时控制进程希望能够拦截另一个进程的所有异常,并能够及 时知道它的状态改变。

linux进程间通信发展历史
? linux进程间通信(IPC)由以下几部分发展而来:

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

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

管道通信
? 管道通信是linux 中比较常见,也比较原始的通信方式之一,它实现了数 据以一种数据流的方式,在多进程间流动。
? 管道:是指用于连接一个读进程和一个写进程,以实现它们之间通信的 共享文件,又称pipe文件。
– 管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程 的标准输出和另一个进程的标准输入连接在一起。
– 写进程在管道的尾端写入数据,读进程在管道的首端读出数据。 – 数据读出后将从管道中移走,其它读进程都不能再读到这些数据。
– 管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前, 进程将一直阻塞。同样,管道已经满时,进程再试图写管道,在其它进程从 管道中移走数据之前,写进程将一直阻塞。
? 管道分类:
– 无名管道: (匿名管道)在系统中没有实名,不能在文件系统中以任何方式看到 该管道,它只是进程的一种资源,会随着进程的结束而被系统清除。
– 有名管道:也称为FIFO管道,是一种文件类型,在文件系统中可以查看到。

匿名管道(pipe)

? 对匿名管道两端进程而言,是一个只存在于内存的特殊文件

? 匿名管道是半双工的,数据只能向一个方向流动

– 一个进程将数据写入管道,另一进程从管道中读取数据

– 写入的内容添加在管道缓冲区的末尾,每次都是从缓冲

区头部读出数据

管 道

?

双向通信的建立

通 – 需要建立起两个管道 信 ? 使用限制

– 只能用于具有亲缘关系的进程之间

? 如父子进程或兄弟进程之间

7

匿名管道的建立

? 基本函数

– int pipe(int fd[2]);

? 参数说明

– fd[2]描述管道两端

? fd[0]只能用于读,称为管道读端



? fd[1]只能用于写,称为管道写端



? 若试图从写端读,或者向读端写都将导致错误发生

通 ? 返回值

信 – 成功时返回0,失败时返回-1

? 说明

– 基本文件I/O函数都可用于管道

? 如close()、read()、write()等

? 低层系统调用

– sys_pipe( )-->do_pipe()

8

匿名管道的读操作
? 进程调用read()系统调用 – 内核最终调用与该文件描述符相关的文件操作表中所找 到的read()方法 – 在管道情形下,read方法将指向pipe_read()函数 – 该系统调用可能以两种方式阻塞当前进程 ? 系统调用开始时管道缓冲区为空 ? 管道缓冲区没有包含所请求的字节(n个字节),写进 程在等待缓冲区的空间时曾经被置为睡眠
9

匿名管道的写操作
? 进程调用write()系统调用
– 内核最终调用pipe_write()函数 – 如果管道没有读进程,写进程发送SIGPIPE信号 – 管道缓冲区一有空闲区域,写进程将试图写入数
据 – 如果读进程不读出管道缓冲区中的数据,那么写
操作将一直阻塞
10

匿名管道示例一

? #include<stdlib.h>

? #include<stdio.h>

? #include<unistd.h>

? int main(void)

?{

?

int fd[2];

?

char str[256];

?

if( pipe(fd)<0 ) {

?

perror("pipe");

?

exit(1);

?

}

?

write(fd[1], "Create the pipe successfully!\n", 31);

?

read(fd[0], str, sizeof(str));

?

printf("%s\n",str);

?

close(fd[0]);

?

close(fd[1]);

?

return 0;

?}

? 单个进程中的管道几乎没有任何用处。通常,调用 p i p e的进 程接着调用 f o r k,这样就创建了从父进程到子进程或反之的 I P C通道。
? f o r k之后做什么取决于我们想要有的数据流的方向。对于从父 进程到子进程的管道,父进程关闭管道的读端(f d [ 0 ]) ,子 进程则关闭写端(f d [ 1 ]) 。对于从子进程到父进程的管道, 父进程关闭 f d [ 1 ],子进程关闭f d [ 0 ]。
12

匿名管道----父子进程间通信

? #include<unistd.h> ? #include<stdio.h> ? #include<stdlib.h> ? #define BUFSZ 256

? else if(pid > 0)

?

{

?

printf("This is farther, write to

fd[1]\n",fd[0],fd[1]);

? int main(void)

?{

?

int fd[2];

?

char buf[BUFSZ];

?

pid_t pid;

?

close(fd[0]);

?

write(fd[1], "farther write,child

read\n", 25);

?

exit(0);

?

}

? ? ? ? ? ? ? ? ? ? ? 13

int len; if( pipe(fd)<0 ) {
perror("failed to pipe"); exit(1); } if( (pid = fork())<0 ) { perror("failed to fork"); exit(1); }

?

else

?

{

?

printf("This is child ,read from

fd[0]\n");

?

close(fd[1]);

?

read(fd[0], buf, BUFSZ);

?

printf("%s\n", buf);

?

}

?

return 0;

?}

匿名管道操作基本流程
? 管道操作的基本流程: 1)使用pipe函数创建管道; 2)用fork函数创建一个子进程; 3)关闭父子进程中不需要的文件描述符,使用管道进行通信;
由于以上对管道的操作是比较规范,也比较常用。所以在ANSI C中将以上操作 定义在两个标准库函数中,分别是popen和pclose函数。 FILE * popen(const char * command, const char * type) ; int pclose(FILE * f p) ;
参数command是一个在shell中可以运行的命令字符串的指针; 参数type是一个字符指针,这个参数只有两种值,分别是r和w,分别对 应popen函数的返回值是一个读打开文件指针,还是写打开文件指针。 函数失败返回NULL,并设置出错变量errno。 pclose函数的参数fp是一个popen打开的文件描述符,函数失败时返回-1。

Popen示例

? #include <unistd.h> ? #include <stdio.h> ? #include <stdlib.h> ? #include <fcntl.h> ? #include <limits.h> ? #define BUFES PIPE_BUF

? while((fgets(buf, BUFES, fp)) != NULL)

?

printf("%s\n", buf);

?

pclose(fp);

?

exit(0);

?}

? int main(void)

?{

?

FILE *fp;

?

char *cmd = "ls -l";

?

char buf[BUFES];

?

if((fp = popen(cmd, "r")) == NULL)

?

{

?

perror("failed to open\n");

?

exit(1);

?

}

?}

有名管道
? 匿名管道缺点 – 没有名字,只能用于具有亲缘关系的进程间通信
? FIFO(有名管道) – 严格遵循先进先出的读写规则 – 有名字,FIFO的名字包含在系统的目录树结构中,支持无亲 缘关系的进程按名字访问
– 类似管道,在文件系统中不存在数据块,而是与一块内核缓 冲区相关联
? read和write操作也由pipe_read()和pipe_write() 实现 ? 与匿名管道主要区别
– FIFO索引节点出现在系统目录树上而不是pipefs特殊文件系统 中
– FIFO是一种双向通信管道,可以以读/写模式打开一 16 个FIFO

有名管道的建立
? 基本函数 – int mkfifo(const char * pathname, mode_t mode);
? 参数说明 – pathname:创建的FIFO名字 – mode:规定FIFO的读写权限
? 返回值 – 成功时返回0 – 失败时返回-1 – 若路径名存在,则返回EEXIST错误
? 说明 – 一般文件的I/O函数都可用于管道,如open(), close(), read(), write()等。
17

有名管道的open()
? 打开规则
– 为读操作而打开FIFO文件
? 若已有进程为写而打开该FIFO,则当前打开操作将成功返回 ? 否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作
设置未设置O_NONBLOCK标志) ? 或立即返回(当前打开操作设置O_NONBLOCK标志)
– 为写操作而打开FIFO文件
? 如果已经有进程为读而打开该FIFO,则当前打开操作将成功返回 ? 否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作
未设置O_NONBLOCK标志) ? 或者,返回ENXIO错误(当前打开操作设置O_NONBLOCK标志)
? 实例:
1,mkfifo testfifo 建立有名管道文件testfifo; 2,ls > testfifo 向有名管道文件写入”ls”命令显示的内容; 18 3,cat testfifo 用”cat”命令读取”testfifo”文件中的内容;

有名管道示例——创建有名管道

? #include<sys/types.h>

?

if((mkfifo(argv[1],mode))<0)

? #include<sys/stat.h> ? #include<errno.h> ? #include<stdio.h> ? #include<stdlib.h>

?

{

?

perror("failed to mkfifo");

?

exit(1);

? #include<unistd.h> ? #include<limits.h> ? #include<fcntl.h> ? int main(int argc, char* argv[])

?

}

?

else

?

printf("you successfully create

?{

a FIFO name is:%s\n",argv[1]);

?

//mode_t mode = 0666;

?

exit(0);

?

mode_t mode = O_NONBLOCK;

?

if( argc!=2 )

?}

?

{

?

printf("USEMSG: create_fifo

{fifoname}\n");

?

exit(1);

? 19 }

有名管道举例——读有名管道

? #include<sys/types.h> ? if((fd=open("testfifo",O_RDONLY))<0)

? #include<sys/stat.h>

?{

? #include<errno.h>

?

perror("open");

? #include<stdio.h>

?

exit(1);

? #include<fcntl.h>

?}

? #include<unistd.h>

while((len=read(fd,buf,BUFES))>0)

? #include<stdlib.h>

?

printf("read: %s",buf);

? #include<limits.h>

? close(fd);

? #define BUFES PIPE_BUF ? return 0;

? int main(void)

?}

?{

? int fd;

?

int len;

? 20 char buf[BUFES];

有名管道示例——写有名管道

#include<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<stdio.h> #include<fcntl.h> #include<unistd.h> #include<stdlib.h> #include<limits.h> #define BUFES PIPE_BUF int main(void) {
int fd; int n, i; if( (fd=open("testfifo",O_WRONLY)) <0) {
perror("open"); exit(1); 21}

for(i=0; i<10; i++) {
if ( write(fd,"message \n",9)<0 )
{ perror("write"); exit(1);
} }
close(fd); return 0; }

信号的本质
? 信号是一种进程间异步通信机制,可以看作是异步通知, 通知接收信号的进程有哪些事情发生了。在实现上是一种 软中断。是对中断的模拟,也称软中断信号或软中断。
– 信号可以导致一个正在运行的进程被异步打断,让进程知道已经 发生了一个特定事件;
– 强迫进程执行自身代码中的信号处理程序; – 发给非运行进程的信号必须由内核保存,直到进程恢复执行; – 阻塞一个信号要求信号的传递被延迟,直到随后解除阻塞; – 每个信号都有名字,这些名字都是以SIG开头; – 不存在编号为0的信号。
22

信号来源
硬件来源 ? 硬件操作,如按Ctrl+C ? 硬件故障 软件来源 ? 常用信号发送函数,如kill(), raise(), alarm() ? 非法运算(浮点运算溢出或内存访问错误等也可产生
信号

信号分类

? 不可靠信号

– 信号值小于SIGRTMIN;

– 进程每次处理信号后,将信号响应函数设置为默认动作, 需调用signal() 重新安装信号 ;

信 号

– 非实时信号都是不可靠信号,不支持排队,信号可能丢 失;

通 信

?

可靠信号

– 信号值介于SIGRTMIN和SIGRTMAX之间;

– 新信号安装函数sigaction()和信号发送函数sigqueue();

– 实时信号都是可靠信号,支持排队;

24

进程对信号的响应方式
? 忽略信号 – 进程忽略接收到的信号,不做任何处理; – SIGKILL和SIGSTOP不能忽略; ? 不能为SIGKILL和SIGSTOP设置新的信号处理函数。
? 捕获信号 – 类似中断处理程序,一旦捕获到相应信号,执行对应 的信号处理函数; – 进程可通过signal()/sigaction()指定进程对某个信号的处 理行为;
? 缺省操作 – Linux内核为信号提供默认处理程序处理;
25

常见信号

?Kill –l可列出所有信号

值 C 语言宏

用途

1 SIGHUP 从终端上发出的结束信号

2 SIGINT 来自键盘的中断信号(Ctrl-c)

3 SIGQUIT 来自键盘的退出信号(Ctrl-\)

8 SIGSTOP 进程收到该信号后会暂停执行,进入暂停态

9 SIGKILL 立即结束程序,不能被阻塞,处理和忽略

14 SIGALRM 进程的定时器到期时,发送该信号

18 SIGCONT 处于阻塞状态的进程收到该信号后会继续执 行

17 SIGCHLD 子进程退出时给父进程发送该信号

26

信号生命周期
? 信号诞生 – 触发信号的事件发生(如检测到硬件异常、定时器超时以及调用信号 发送函数kill()或sigqueue()等)。
? 信号在进程中注册 – 内核更新目标进程的数据结构,将信号加入进程的未决信号集,并将 信号所携信息保存到某个sigqueue结构中 ? 对实时信号,不管其是否已在进程中注册,都被再次注册 ? 对非实时信号,若该信号已在进程中注册,则将其丢弃
? 信号在进程中注销 – 进程从核心空间返回用户空间时都检测是否有信号等待处理 ? 若存在等待处理且未被阻塞的未决信号,则将其在未决信号链中 占有的结构卸掉(对实时进程只删除sigqueue结构)
? 信号处理函数执行完毕 27 – 执行相应信号处理函数或改变目标进程的执行状态

信号在进程中注册
? 进程的task_struct结构中有关于本进程中未决信号的数据成员:
struct sigpending pending; struct sigpending{
struct sigqueue *head, **tail; sigset_t signal; };
? struct sigpending中第三个成员signal是进程中所有未决信号集, 第一、第二个成员分别指向一个sigqueue类型的链表的(称之 为“未决信号信息链”)的首尾,该链中的每个sigqueue结构包 含一个特定信号所携带的信息,并指向下一个sigqueue结构:
struct sigqueue{ struct sigqueue *next; siginfo_t info;
} 28

信号在进程中注销
? 在目标进程执行过程中,会检测是否有信号等待处理(每次从 系统空间返回到用户空间时都做这样的检查)。如果存在未决 信号等待处理且该信号没有被进程阻塞,则在运行相应的信号 处理函数前,进程会把信号在未决信号链中占有的结构卸掉。 是否将信号从进程未决信号集中删除对于实时与非实时信号是 不同的。对于非实时信号来说,由于在未决信号信息链中最多 只占用一个sigqueue结构,因此该结构被释放后,应该把信号 在进程未决信号集中删除(信号注销完毕);而对于实时信号 来说,可能在未决信号信息链中占用多个sigqueue结构,因此 应该针对占用sigqueue结构的数目区别对待:如果只占用一个 sigqueue结构(进程只收到该信号一次),则应该把信号在进 程的未决信号集中删除(信号注销完毕)。否则,不应该在进 程的未决信号集中删除该信号(信号注销完毕)。
? 进程在执行信号相应处理函数之前,首先要把信号在进程中注 销。

信号处理函数
? 信号发送函数
– kill(), sigqueue(), raise(), alarm(), setitimer(), pause(),abort()
? 信号安装函数
– signal(), sigaction()
? 信号集操作函数
– sigemptyset(), sigfillset(), sigaddset(), sigdelset(), sigismember()
30

信号发送函数—kill()
? #include <sys/types.h> #include <signal.h> int kill(pid_t pid,int signo)
? 功能 – 向进程或进程组发送一个信号 (成功返回 0; 否则,返回 -1 )
? 参数说明 – pid:接收信号的进程(组)的进程号 ? pid>0:发送给进程号为pid的进程 ? pid=0:发送给当前进程所属进程组里的所有进程 ? pid=-1:发送给除1号进程和自身以外的所有进程 ? pid<-1:发送给属于进程组-pid的所有进程 – signo:发送的信号 ? Signo = 0:不发送信号,可用于检查目标进程是否存在,以及当前进 程是否具有向目标进程发送信号的权限(root权限的进程可以向任何 进程发送信号,非root权限的进程只能向属于同一个session或者同一 个用户的进程发送信号)。
31

? #include <sys/wait.h>

? #include <sys/types.h>

? #include <stdio.h>

? #include <stdlib.h>

? #include <signal.h>

? int main( void ) {

? pid_t childpid;

? int status;

? int retval;

? childpid = fork();

? if ( -1 == childpid ){

?

perror( "fork()" );

?

exit( EXIT_FAILURE );

?}

? else if ( 0 == childpid ){

?

puts( "In child process" );

?

sleep( 100 );//让子进程睡眠,看

看父进程的行为

?

exit(EXIT_SUCCESS);

?}

? else{

?

if ( 0 == (waitpid( childpid, &status,

WNOHANG ))){

?

retval = kill( childpid,SIGKILL );

?

if ( retval ){

?

puts( "kill failed." );

?

perror( "kill" );

?

waitpid( childpid, &status, 0 );

?

}

?

else{

?

printf( "%d killed\n", childpid );

?

}

?

}

?}

? exit(EXIT_SUCCESS);

?}

信号发送函数—sigqueue()
? #include <sys/types.h> #include <signal.h> int sigqueue(pid_t pid, int signo, const union sigval sigval_t)
? 调用成功返回 0;否则,返回 -1。 ? 功能
– 主要针对实时信号,支持带有参数信号,与函数sigaction()配合使用 ? 参数说明
– pid:接收信号的进程ID – signo:待发送信号 – sigval_t:信号传递的参数(4字节) ? 说明 – 调用sigqueue()时,sigval_t被拷贝到信号处理函数 – sigqueue()发送非实时信号时
? sigval_t包含的信息仍然能够传递给信号处理函数 ? 仍然不支持排队,所有相同信号都被合并为一个信号
33

sigqueue()向信号传递附加消息的流程
? sigqueue()的第三个参数是sigval联合数据结构 typedef union sigval { int sival_int; void *sival_ptr; }sigval_t;
– 调用sigqueue()时,该数据结构中的数据将被拷贝到信号 处理函数sigaction()的第二个参数
– 这样,就可在发送信号的同时让信号传递一些附加信息
34

Kill()与sigqueue()区别
– sigqueue()比kill()传递更多的附加信息 – sigqueue()只能向一个进程发送信号
信 号 通 信
35

信号发送函数—raise()

?

?

?



号 通

?



?

#include <signal.h> int raise(int signo) 功能 – 向自身发送信号 参数说明 – signo:发送的信号
? signo=0:不发送信号 返回值 – 成功时,返回0 – 错误时,返回-1 说明 – raise()可以通过kill( )实现,raise(signo)等价于
? kill(getpid( ), signo);

36

? #include <stdio.h> ? #include <signal.h>

? int main(void)

?{

?

printf("kill myself!\n");

?

raise(SIGKILL);

?

printf("can you see this?\n");

?

return 0;

?}

信号发送函数—alarm()

? #include <unistd.h>

unsigned int alarm(unsigned int seconds)

? 功能

– 设置定时器,当计时时间到达时,向进程发出SIGALRM信号

? 参数说明

– seconds:等待秒数



? seconds为0时,取消先前设置的闹钟,返回闹钟剩余时间



?若之前未设闹钟,则返回0

通 ? 说明

信 – alarm()只发送一次信号

– 若需重复设定定时器,则要多次调用alarm()函数

38

#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h> #include <signal.h> void handler() {
printf("hellon\n"); } main() {
int i; signal(SIGALRM,handler); alarm(5); for(i=1;i<7;i++){
printf("sleep %d \n",i); sleep(1); } }

信号发送函数—setitimer()
? 函数原型 – int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
? 参数说明 – which:逻辑定时器类型 ? ITIMER_REAL:按实际时间计时,计时到达时向进程发送SIGALRM信 号。
? ITIMER_VIRTUAL:计算进程在用户态执行的时间,计时到达将发送 SIGVTALRM信号给进程。
? ITIMER_PROF:计算进程在用户态和核心态的总时间。计时到达将 发送SIGPROF信号给进程。 ?与ITIMER_VIRTUAL是一对,该定时器经常用来统计进程在用户 态和核心态花费的时间
– value:指明定时器的时间 – ovalue:如果不为空,则保存上次调用设定的值 ? 功能 – 指定一段时间后,执行某个function; 40 – 每间格一段时间就执行某个function

信号发送函数—setitimer() (续)

? 返回值

– 成功时,返回0

– 错误时,返回-1

? struct itimerval结构定义

struct itimerval {

struct timeval it_interval; /*定时器周期*/



struct timeval it_value; /*定时器剩的时间,为0时发信号*/



};

通 ? struct timeval结构定义



struct timeval {

long tv_sec; /*秒*/

long tv_usec; /*微秒,1秒=1000000微秒*/

};

it_value为0是不会触发信号的,所以要能触发信号,it_value得大于0;

如果it_interval为零,只会延时,不会定时(也就是说只会触发一次信

号)。

41

信号发送函数—setitimer()示例

? #include <stdio.h>

? int main(int argc, char *argv[])

? #include <signal.h>

?{

? #include <sys/time.h>

? signal(SIGALRM, signalHandler);

? struct itimerval new_value,

? void signalHandler(int signo) old_value;

?{ ? switch (signo){

? new_value.it_value.tv_sec = 1; ? new_value.it_value.tv_usec = 0; ? new_value.it_interval.tv_sec = 2;

?

case SIGALRM:

? new_value.it_interval.tv_usec = 0;

?

printf("Caught the ? setitimer(ITIMER_REAL,

SIGALRM signal!\n");

&new_value, &old_value);

?

break;

? for(;;);

?}

? return 0;

?}

?}

42

信号发送函数—pause()
? 功能
– 使当前进程暂停,进入睡眠状态,直到被信号 所中断,即将进程挂起等待信号到来。
? 函数原型
– int pause(void);
? 返回值
– -1
? 举例
– 使用alarm( )和pause( )实现sleep( )功能
43

信号发送函数—pause()示例
44

信号的安装(设置信号关联动作)
? 如果进程要处理某一信号,那么就要在进程中安装该 信号。安装信号主要用来确定信号值及进程针对该信 号值的动作之间的映射关系,即进程将要处理哪个信 号;该信号被传递给进程时,将执行何种操作。
? linux主要有两个函数实现信号的安装:signal()、 sigaction()。其中signal()在非可靠信号系统调用的基础 上实现, 是库函数。它只有两个参数,不支持信号传递 信息,主要是用于前32种非实时信号的安装;而 sigaction()是较新的函数(由两个系统调用实现: sys_signal以及sys_rt_sigaction),有三个参数,支持信 号传递信息,主要用来与 sigqueue() 系统调用配合使 用,当然,sigaction()同样支持非实时信号的安装。 sigaction()优于signal()主要体现在支持信号带有参数。

信号安装函数—signal()
? 原型定义
– void (*signal(int signum, void (*handler)(int)))(int); ? 参数说明
– signum:需要安装的信号 – handler:与安装信号相关的处理函数,可以是SIG_IGN
或SIG_DFL ? SIG_IGN:忽略该信号 ? SIG_DFL:执行默认操作函数
? 返回值 – 成功时,返回新安装信号处理函数handler的值 – 失败时,返回SIG_ERR
? 底层系统调用
46 – sys_signal(int sig, __sighandler_t handler)

信号安装函数—signal()示例

? #include <stdio.h>

? #include <stdlib.h>

? #include <unistd.h>

? #include <signal.h>

? void sig_usr(int sig);

? int main(int argc, char* argv[])

?{

?

int i=0;

?

if(signal(SIGUSR1, sig_usr) == SIG_ERR)

printf("Can't catch SIGUSR1\n");

if(signal(SIGUSR2, sig_usr) == SIG_ERR)

?

printf(“Can‘t catch SIGUSR2\n”);

?

while(1) {

?

printf("%2d\n", i);

?

pause();

?

i++;

?

}

?

return 0;

47

?}

? void sig_usr(int sig)

?{

?

if(sig == SIGUSR1)

?

printf("Received SIGUSR1\n");

?

else if(sig == SIGUSR2)

?

printf("Received SIGUSR2\n");

?

else

?

printf("Undeclared signal %d \n",sig);

?}

执行:

? Gcc signal.c –o signal

? ./signal&(以后台程序运行,并显示pid)

? Kill –SIGUSR1 pid

信号安装函数—sigaction()
? 原型定义 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); sigaction函数用于设定进程接收到特定信号后的行为。 – 第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有 效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。 – 第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实 例中,指定了对特定信号的处理,如果为空,进程会以缺省方式对信号 处理; – 第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定 oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用 于检查信号的有效性。 – 第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信 息、信号处理函数执行过程中应屏蔽掉哪些函数等等。
? 底层系统调用 – sys_sigaction(int sig, const struct old_sigaction __user *act, struct old_sigaction __user *oact)
49

? sigaction结构定义如下:

struct sigaction {

union{

__sighandler_t _sa_handler;

void (*_sa_sigaction)( int, struct siginfo *, void *);

}_u

sigset_t

sa_mask;

unsigned long sa_flags;

void (*sa_restorer)(void);

}

? sa_restorer,已过时,POSIX不支持它,不应再被使用。
? 联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指 定信号关联函数,即用户指定的信号处理函数。除了可以是 用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理 方式),也可以为SIG_IGN(忽略信号)。

? 由_sa_handler 指定的处理函数只有一个参数,即信号值,所以 信号不能传递除信号值之外的任何信息;
? 由_sa_sigaction 指定的信号处理函数带有三个参数,是为实时 信号而设的(当然同样支持非实时信号),它指定一个3参数 信号处理函数。第一个参数为信号值,第三个参数没有使用( posix没有规范使用该参数的标准),第二个参数是指向 siginfo_t 结构的指针,结构中包含信号携带的数据值,参数所 指向的结构如下:

siginfo_t { int si_signo; /* 信号值,对所有信号有意义*/ int si_errno; /* errno值,对所有信号有意义*/ int si_code; /* 信号产生的原因,对所有信号有意义*/ pid_t si_pid; /* 发送信号的进程ID,对实时信号以及SIGCHLD有
意义 */ uid_t si_uid; /* 发送信号进程的真实用户ID,对kill(2),实时信号以及
SIGCHLD有意义 */ int si_status; /* 退出状态,对SIGCHLD有意义*/ clock_t si_utime; /* 用户消耗的时间,对SIGCHLD有意义 */ clock_t si_stime; /* 内核消耗的时间,对SIGCHLD有意义 */ sigval_t si_value; /* 信号值,对所有实时有意义,是一个联合数据结构,
/*可以为一个整数(由si_int标示,也可以为一个指针,由si_ptr标示)*/ void * si_addr; /* 触发fault的内存地址,对
SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义*/ int si_band; /* 对SIGPOLL信号有意义 */ int si_fd; /* 对SIGPOLL信号有意义 */
}

? sa_mask:信号的集合。指定在信号处理程序执行过程中,哪些 信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号 的嵌套发送。
? 注:sigaction()安装信号的处理函数执行过程中由sa_mask指 定的信号才被阻塞。
? sa_flags用于更改指定信号的行为。比较重要的标志位是 SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以 被传递到信号处理函数中,因此,应该为sigaction结构中的 sa_sigaction指定处理函数,而不应该为sa_handler指定信号处 理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction 指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数 同样不能得到信号传递过来的数据,在信号处理函数中对这些 信息的访问都将导致段错误(Segmentation fault)。

信号安装函数—sigaction()示例一

? #include <stdio.h>

? #include <stdlib.h>

? #include <unistd.h>

? #include <signal.h>

? void myFun(int sig);

? int main(int argc, char* argv[])

?{

?

struct sigaction act, oldact;

?

act.sa_handler = myFun;

?

sigemptyset(&act.sa_mask);

?

act.sa_flags = 0;

?

sigaction(SIGUSR1, &act, &oldact);

?

while(1){

?

printf("Hello world!\n");

?

pause();

?

}

?}
54

? void myFun(int sig)

?{

?

printf("I got a

signal:%d\n",sig);

?}

? Gcc sigacton1.c –o sigaction ? ./sigaction& ? Kill –SIGUSR1 pid

信号安装函—sigaction()数示例二

? #include <stdio.h> ? #include <unistd.h> ? #include <stdlib.h> ? #include <signal.h>

? void func(int signo, siginfo_t *info, void *p)

?{

?

printf("signo = %d\n", signo);

?

printf("sender pid = %d\n", info-

>si_pid);

?}

? int main()

?{

?

struct sigaction act, oldact;

?

sigemptyset(&act.sa_mask);

?

act.sa_flags = SA_SIGINFO;

?

act.sa_sigaction = func;

?

sigaction(SIGUSR1, &act,

&oldact);

?

while(1)

?

{

?

printf("pid is %d

hello!\n",getpid());

?

pause();

?

}

?}

? 在另外一个终端发送信号给当前 进程

sigqueue()函数调用示例在不同进程间传递整型参数.信号接收程序

– #include <stdio.h> – #include <stdlib.h> – #include <unistd.h> – #include <signal.h> – #include <sys/types.h> – void myFun(int, siginfo_t*,
void* myfun); – int main(int argc, char* argv[]) –{ – struct sigaction act; – int sig; – pid_t pid; – pid = getpid(); – printf("pid is%d\n",pid);
– sigemptyset(&act.sa_mask); – act.sa_sigaction = myFun; – act.sa_flags = SA_SIGINFO;

– if(sigaction(SIGUSR1, &act, NULL)<0)

–{



printf("install sig error\n");



return 0;

–}

– while(1)

–{



sleep(2);



printf("wait for the signal\n");

–}

–}

– void myFun(int signo, siginfo_t *info, void*

myfun)

–{

– printf("the int value is %d\n",info->si_int);

– printf("Recv signum is%d\n",signo);

– //raise(SIGKILL);

–}

56

sigqueue()函数调用示例在不同进程间传递整型参数.信号发送程序

? #include <stdio.h>

? #include <stdlib.h>

? #include <unistd.h>

? #include <signal.h>

? #include <sys/types.h>

? #include <sys/time.h>

? main(int argc, char* argv[])

?{

?

pid_t pid;

?

int signum;

?

union sigval mysigval;

pid = (pid_t)atoi(argv[1]);

?

mysigval.sival_int = 8;

?

if(sigqueue(pid, SIGUSR1, mysigval)==-1)

?

printf("send error\n");

?

sleep(2);

?

return 0;

?}

57

signal()与sigaction()的区别
? 不同点 – signal() ? 安装的信号不能向信号处理函数传递信息 – sigaction() ? 可设置进程的信号掩码,返回设置之前的sigaction结构 ? 安装的信号可以向信号处理函数传递信息
? 相同点 – 都可以为指定的信号设置信号处理函数 – 共用同一个内核函数do_sigaction()
58

? 信号集定义

信号集操作函数

? 函数原型 – int sigemptyset(sigset_t *set); ? 初始化信号集合set,将set设置为空 – int sigfillset(sigset_t *set); ? 初始化信号集合,将信号集set设置为包含所有信号的集合 – int sigaddset(sigset_t *set, int signo); ? 将信号signo加入到信号集set中 – int sigdelset(sigset_t *set, int signo); ? 将信号signo从信号集set中删除 – int sigismember(sigset_t *set, int signo); ? 查询信号signo是否在信号集set中
59

信号操作函数—sigprocmask( )
? 功能 – 通过信号集set修改进程的信号阻塞集
? 函数原型 – int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
? 参数 – how:函数操作方式 ? SIG_BLOCK:增加一个信号集到当前进程的阻塞集中 ? SIG_UNBLOCK:从当前阻塞集中删除一个信号集 ? SIG_SETMASK:将当前信号集设置为信号阻塞集合 – set:当前进程的信号集 – oset:保存当前进程的信号阻塞集
? 说明 – 在使用之前需先设置好信号集合set
60

其他信号处理函数
? sigpending(sigset_t *set)) – 获取当前进程的未决信号集 – set保存返回结果
? sigsuspend(const sigset_t *mask)) – 在接收到某个信号之前,临时用mask替换进程的信号 掩码,并暂停进程执行,直到收到信号为止 – sigsuspend返回后将恢复调用之前的信号掩码 – 信号处理函数完成后,进程将继续执行 – 该系统调用始终返回-1,并将errno设置为EINTR
61

task_struct中与信号处理相关的成员
? struct signal_struct *signal – 信号描述符结构,为每种信号选择处理函数
? struct sighand_struct *sighand – 包含信号处理函数描述符的数组,信号作为数组序号
? sigset_t blocked, real_blocked; – 被阻塞信号的掩码,每种信号类型对应一个元素
? struct sigpending pending; – 维护本进程中的未决信号
? sigset_t saved_sigmask; – 保存信号掩码 – 定义TIF_RESTORE_SIGMASK时恢复信号掩码
62

task_struct信号处理相关数据结构关系图
63

System V IPC概述
? IPC资源 – 表示单独的消息队列、共享内存或信号量集合
? 具有相同类型的接口函数 – 信号量集合 实现进程同步
– 消息队列以异步方式为通信频繁、但数据量少的进程通 信提供服务
– 共享主存为数据量大的进程间通信提供服务 ? 共同点
– 通过 System V IPC对象通信时,需传递该对象的唯一IPC标 识符
– 访问System V IPC对象时必须经过许可检验 – System V IPC对象的访问权限由对象创建者设置
64

IPC对象标识符与IPC键
? IPC标识符 – 由内核分配给IPC对象,在系统内部唯一 – IPC对象标识符的获取:XXXget() ? 将IPC键传递给以sys_打头的内核函数,并为用户分配一 个与IPC对象相对应的数据结构 ? 返回一个32位IPC标识符,进程使用此标识符对该资源进 行访问
? IPC键 – IPC对象的外部表示,可由程序员选择 – 如果键是公用的,则系统中所有进程通过权限检查后,均 可找到和访问相应IPC对象 – 每个进程都可建立一个键值为IPC_PRIVATE的私有IPC对象
65

IPC键的创建
? 创建函数
– key_t ftok( char * filename, int id);
? 功能说明
– 将一个现存文件名(对应文件必须是可访问的) 和一个整数标识符id转换成一个key_t值
– 在Linux系统中,调用该函数时,系统将该文件 的索引节点号取出,并在前面加上子序号,从 而得到key_t返回值
66

IPC资源的全局定义
? 消息队列 – msgid_ds : 16个 – 定义位置:/proc/sys/kernel/msgmni
? 共享内存 – shmid_ds : 4096个 – 定义位置:/proc/sys/kernel/shmmni
? 信号量 – semid_ds:128个 – 定义位置:/proc/sys/kernel/sem
67

System V IPC的基本操作
? 操作函数(XXX代表msg、sem、shm三者之一) – XXXget():获得IPC标识符 – XXXctl():控制IPC资源
? 操作模式 – 先通过XXXget()创建一个IPC资源,返回IPC标识符 – 随后操作均以IPC资源标识符为参数,对相应IPC资源进 行操作 – 其他进程可通过XXXget()获取已有的IPC资源标识符(权 限允许的话),并对其操作
68

请求IPC标识符时返回的错误码

错误码
EACCESS EEXIST
EINVAL ENOENT
ENOMEM ENOSPC

说明
进程没有适当的访问权限 进程试图创建一个和已有关键字相同的IPC 资源 在XXXget()函数中出现非法参数 不存在与请求关键字对应的IPC资源,而且 进程没有请求创建这个资源 没有更多的存储空间供IPC其他的资源使用 已经超过了IPC资源最大数目的限制
69

主要内容
? 传统进程通信
– 信号通信 – 管道通信
? System V IPC进程通信
– 消息队列 – 共享主存 – 信号量
70

消息队列
? 消息队列(也叫做报文队列)是一个消息的链表。可以把消息 看作一个记录,具有特定的格式以及特定的优先级。对消息队 列有写权限的进程可以向消息队列中按照一定的规则添加新消 息;对消息队列有读权限的进程则可以从消息队列中读走消息。
? IPC消息队列资源的限制 – IPC消息队列的缺省数为16 – 每个消息的缺省最大值8192字节 – 队列中全部信息的缺省大小为16384字节
71

消息队列模型

操作消息队列
1、 打开或创建消息队列. 2、 读写操作:消息读写操作非常简单,对开发人员来说,每个消息都类似如下的数据 结构:
struct msgbuf{ long mtype; char mtext[1];
};
? mtype成员代表消息类型,从消息队列中读取消息的一个重要依据就是消息的类型 ;mtext是消息内容,当然长度不一定为1。对于发送消息来说,首先预置一个 msgbuf缓冲区并写入消息类型和内容,调用相应的发送函数即可;对读取消息来说 ,首先分配这样一个msgbuf缓冲区,然后把消息读入该缓冲区即可
3、 获得或设置消息队列属性:
? 消息队列API共有四个,使用时需要包括几个头文件:
– #include <sys/types.h>
– #include <sys/ipc.h>
– #include <sys/msg.h>

消息队列的基本操作—msgget()
? 功能 – 创建一个新消息队列或打开一个存在的队列
? 函数原型
– int msgget(key_t key, int flag); ? 参数说明
– key:待创建/打开队列的键值,如果key值为IPC_PRIVATE 则创建一个新的消息队列。
– flag:创建/打开方式 ? IPC_CREAT:如果存在与当前key值相同的消息队列,则 返回该消息队列id。如果不存在,则创建一个新的消息 队列。 ? IPC_EXCL:如果存在与当前key值相同的消息队列,则 返回失败。
? 返回值 – 成功返回消息队列描述符,否则返回-1
74

ftok函数
? ftok原型:
key_t ftok( char * fname, int id )
? 参数:
fname指定的文件名(该文件必须是存在而且可以访问的), id是子序号,虽然为int,但是只有8个比特被使用(0-255)。
? 返回值:
当成功执行的时候,一个key_t值将会被返回,否则 -1 被 返回。
? 在一般的UNIX实现中,是将文件的索引节点号取出,前面 加上子序号得到key_t的返回值。如指定文件的索引节点号 为65538,换算成16进制为 0x010002,而你指定的ID值为 38,换算成16进制为0x26,则最后的key_t返回值为 0x26010002。

消息队列的基本操作—msgrcv()
? 函数原型 – ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t size, long type, int flag);
? 功能 – 该系统调用从msqid代表的消息队列中读取一个消息,并把 消息存储在msgp指向的msgbuf结构中。
? 参数说明 msqid:消息队列描述字,描述从哪个消息队列读取消息 msgp:消息存储位置 size:消息内容的长度(mtext[]) type:请求读取的消息类型
? 根据type的不同分成三种情况处理
– type=0:接收该队列的第一个消息,并将它返回给调用者
– type>0:接收类型type的第一个消息
– type<0:接收小于等于type绝对值的最低类型的第一个消息
76

消息队列的基本操作—msgrcv()工作流程
flag:规定队列无消息时内核应做的操作 ? IPC_NOWAIT:如果现在没有消息,调用进程立即返回, 同时返回-1。 ? IPC_EXCEPT:type>0时使用,返回第一个类型不为type 的消息 ? IPC_NOERROR:如果队列中满足条件的消息内容大于所 请求的size字节,则把该消息截断,截断部分将丢失。 如果没有设置IPC_NOERROR,而消息又太长,则出错返 回E2BIG,此时消息仍留在队列中。
? 调用返回: 成功返回读出消息的实际字节数,否则返回-1。
? 注意: 取消息的时候并不一定按照先进先出的次序取消息,可以按 照消息的类型字段取消息。
77

消息队列的基本操作—msgsnd()
? 函数原型 – int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsize, int flag);
? 功能 – 向msqid代表的消息队列发送一个消息,即将发送的消息存储在msgp指 向的msgbuf结构中,消息的大小由msgze指定。
? 参数说明 – 对发送消息来说,有意义的flag标志为IPC_NOWAIT,指明在消息队列没 有足够空间容纳要发送的消息时,msgsnd是否等待。
? 造成msgsnd()等待的条件: – 当前消息的大小与当前消息队列中的字节数之和超过了消息队列的总容 量;
? msgsnd()解除阻塞的条件有三个: – 消息队列中有容纳该消息的空间; – msqid代表的消息队列被删除; – 调用msgsnd()的进程被信号中断;
? 调用返回: – 成功返回0,否则返回-1。
78

消息队列的基本操作—msgctl()
? 函数原型
– int msgctl(int msqid, int cmd, struct msqid_ds *buf); ? 功能
– 该系统调用对由msqid标识的消息队列执行cmd操作,共有三 种cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
– IPC_STAT:该命令用来获取消息队列信息,返回的信息存贮 在buf指向的内存中;
– IPC_SET:该命令用来设置消息队列的属性,要设置的属性存 储在buf指向的msqid_ds结构中;可设置属性包括: msg_perm.uid、msg_perm.gid、msg_perm.mode以及 msg_qbytes。
– IPC_RMID:删除msqid标识的消息队列; ? 调用返回:成功返回0,否则返回-1。
79

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/msg.h>

? #include <string.h>

? struct msg{

?

long msg_types;

?

char msg_buf[512];

? };

? int main()

?{

?

int qid;

?

int pid;

?

int len;

?

struct msg pmsg;

?

pmsg.msg_types = getpid();

?

sprintf(pmsg.msg_buf, "hello!this

is:%d\n",getpid());

?

len = strlen(pmsg.msg_buf);

? //key_t key;

? //key = ftok(“usr/local/test”, 30);

? if((qid = msgget(0x66, IPC_CREAT | 0666))<0)

?

{

?

perror("msgget");

?

exit(1);

?

}

?

if((msgsnd(qid, &pmsg, len, 0))<0)

?

{

?

perror("msgsnd");

?

exit(1);

?

}

?

printf("successfully send a message

to the queue:%d\n", qid);

?

system("ipcs -q");

?

return 0;

?}

? #include <stdio.h>

? len = msgrcv(qid, &pmsg, BUFSIZE, 0, 0);

? #include <stdlib.h> ? #include <sys/types.h> ? #include <sys/ipc.h> ? #include <sys/msg.h> ? #define BUFSIZE 4096

?

if(len > 0){

?

pmsg.msg_buf[len] = '\0';

?

printf("recving que

id:%ld\n",qid);

?

printf("message type:%d\n",

pmsg.msg_types);

? struct msg{

?

long msg_types;

?

char msg_buf[511];

? };

? int main(int argc, char* argv[])

?

printf("message

length:%d\n",len);

?

printf("message

text:%s\n",pmsg.msg_buf);

? }else if(len == 0)

?

printf("no message!");

?{

? else{

?

int qid, len;

?

perror("msgrcv");

?

struct msg pmsg;

?

exit(0);

? qid = msgget(0x66,IPC_CREAT | 0666); ?

}

? //key_t key; ? //key = ftok(“usr/local/test”, 30);

? system("ipcs -q"); ? exit(0); ?}

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/msg.h>

? #include <string.h>

? struct msg{

?

long msg_types;

?

char msg_buf[512];

? };

? int main()

?{

?

int qid;

消息队列使用示例——发送

? if((qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666))<0)

?

{

?

perror("msgget");

?

exit(1);

?

}

?

if((msgsnd(qid, &pmsg, len, 0))<0)

?

{

?

perror("msgsnd");

?

exit(1);

?

}

?

printf("successfully send a message to

the queue:%d\n", qid);

?

int pid;

? System(“ipcs -q”);

?

int len;

?

return 0;

?

struct msg pmsg;

?}

?

pmsg.msg_types = getpid();

?

sprintf(pmsg.msg_buf, "hello!this

is:%d\n",getpid());

? 82 len = strlen(pmsg.msg_buf);

消息队列使用示例——接收

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/msg.h>

? #define BUFSIZE 4096

? struct msg{

?

long msg_types;

?

char msg_buf[511];

? };

? int main(int argc, char* argv[])

?{

? if(len > 0){

?

pmsg.msg_buf[len] = '\0';

?

printf("recving que

id:%ld\n",qid);

?

printf("message type:%d\n",

pmsg.msg_types);

?

printf("message

length:%d\n",len);

?

printf("message

text:%s\n",pmsg.msg_buf);

? }else if(len == 0)

?

int qid,len;

?

printf("no message!");

?

struct msg pmsg;

? else{

?

if(argc<2){

?

printf("parameters error < 2\n");

?

exit(1);

?

}

?

qid = atoi(argv[1]);

?

len = msgrcv(qid, &pmsg, BUFSIZE, 0, 0);

83

?

perror("msgrcv");

?

exit(1);

?

}

? system("ipcs -q");

? exit(0);

?}

主要内容
? 传统进程通信
– 信号通信 – 管道通信
? System V IPC进程通信
– 消息队列 – 共享主存 – 信号量
84

共享内存概述
? 基本思想 – 多个进程共享的一块内存区域 ? 需访问共享内存的进程必须在自身地址空间增加一个新内存区,映 射与共享内存区相关的页框 ? 共享内存区的进程对该区域的操作是互见的
? IPC共享内存资源的限制 – IPC共享内存的缺省区域数为4096 – 每个共享段的缺省大小为32MB
85

共享主存基本操作—shmget()
? 功能 – 创建/获得一个共享主存区
? 函数原型 – int shmget(key_t key, int size, int flag);
? 参数说明 – key:共享主存区键值.当key的取值为IPC_PRIVATE,则函数shmget()将创建 一块新的共享内存;如果key的取值为0,而参数flag中设置了IPC_CREAT这 个标志,则同样将创建一块新的共享内存。 – size:共享内存大小(以字节计) ? 创建新共享内存:必须指定size ? 访问现存共享内存:size=0 – Flag:函数的行为参数,指定当函数遇到阻塞或其他情况时应做出的反应。
? 返回值 – 成功返回创建的共享内存的标示符 – 失败则返回-1
86

共享主存基本操作—shmat()
? 功能 – 将共享主存区映射到进程虚拟地址空间
? 函数原型
– void *shmat(int shmid, char *addr, int flag); ? 参数说明
– shmid:需要映射的共享内存标识符; – addr:
=NULL:表示映射地址由内核自动选择(推荐方法) ; !=NULL:映射地址取决于flag的值;
?flag!=SHM_RND,映射地址等于addr指定的地址; ?指定SHM_RND,进位到最低可用地址;
– flag SHM_RND: SHM_RDONLY:以只读方式映射到进程的地址空间; SHM_REMAP:替代掉与指定参数重叠的现存共享区段的映射;
? 返回值 – 成功时返回映射区起始地址;
87 – 出错时为-1;

共享主存基本操作—shmdt()

? 功能

– 把一个共享主存区从指定进程的虚地址空间断开

? 函数原型

– int shmdt(char *addr);

共 享

?

参数说明

主 存

– addr:需断开连接的虚地址,即由shmat( )返回 的虚地址。

? 返回值

– 调用成功返回0

– 失败时返回-1

88

共享主存属性基本操作—shmctl()
? 功能 – 控制共享主存区属性,读取或修改其状态信息
? 函数原型
– int shmctl(int shmid, int cmd, struct shmid_ds *buf); ? 参数说明
– shmid:共享存储段的id; – buf: 用户缓冲区地址 ;
– cmd ? IPC_STAT:读取shmid_ds结构,并存放在由buf指向的结构中 ? IPC_SET:按buf的值设置与此段相关结构中的下列3个字段: shm_perm.uid、shm_perm.gid以及shm_perm.mode ? IPC_RMID:从系统中删除该共享存储段 ? SHM_LOCK:锁住共享存储段 ? SHM_UNLOCK:解锁共享存储段
? 返回值 – 调用成功返回0 – 失败返回-1
89

创建共享内存

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/shm.h>

? #include <stdio.h>

? #include <stdlib.h>

? #define BUFSZ 2048

? int main()

?{

?

int shmid;

?

char *shmadd;

?

if((shmid=shmget(IPC_PRIVATE,BUFSZ,0666))<0) {

?

perror("shmget");

?

exit(1);

?

}

?

else

?

printf("created shared-memory: %d\n",shmid);

?

system("ipcs -m");

? if((shmadd=shmat(shmid,0,0))<(char *)0)

?{

?

perror("shmat");

?

exit(1);

?}

? else

?

printf("attached shared-memory\n");

? system("ipcs -m"); /*显示示系统内存情况*/

? if((shmdt(shmadd))<0) {/*断开共享内存映射*/

?

perror("shmdt");

?

exit(1);

?}

? else

?

printf("deleted shared-memory\n");

? system("ipcs -m");

? exit(0);

?}

写共享内存 示例一

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/shm.h>

? #include <stdio.h>

? #include <stdlib.h>

? #include <error.h>

? #define SHM_SIZE 4096

? #define SHM_MODE (SHM_R | SHM_W) /* user read/write */

? int main(void) {

?

int shmid;

?

char *shmptr;

?

if ( (shmid = shmget(0x44, SHM_SIZE, SHM_MODE | IPC_CREAT)) < 0)

?

perror("shmget");

?

if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1) perror("shmat");

?

sprintf(shmptr, "%s", "hello, world"); /* 往共享内存写数据 */

?

exit(0);

?}

读共享内存 示例一

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/shm.h>

? #include <stdio.h>

? #include <error.h>

? #include <stdlib.h>

? #define SHM_SIZE 4096

? #define SHM_MODE (SHM_R | SHM_W | IPC_CREAT) /* user read/write */

? int main(void) {

?

int shmid;

?

char *shmptr;

? if ( (shmid = shmget(0x44, SHM_SIZE, SHM_MODE)) < 0)

?

perror("shmget");

? if ( (shmptr = shmat(shmid, 0, 0)) == (void *) -1)

perror("shmat");

? printf("%s\n", shmptr); /* 从共享内存读数据 */

? exit(0);

?}

写共享内存 示例二

? #include <sys/ipc.h>

? #include <sys/shm.h>

? #include <sys/types.h>

? #include <unistd.h>

? #include <string.h>

? typedef struct{

?

char name[4];

?

int age;

? } people;

? main(int argc, char** argv){

?

int shm_id,i;

?

key_t key;

?

char temp;

?

people *p_map;

?

char* name = "/usr/local/2013_autumn";

?

key = ftok(name,0);

?

if(key==-1) perror("ftok error");

? shm_id=shmget(key,4096,IPC_CREAT);

? if(shm_id==-1){

?

perror("shmget error");

?

return;

?}

? printf("sharemem id is:%d\n",shm_id);

? p_map=(people*)shmat(shm_id,NULL,0);

? temp='a';

? for(i = 0;i<10;i++){

memcpy((*(p_map+i)).name,&temp,1);

?

(*(p_map+i)).age=20+i;

?

temp+=1;

?}

? if(shmdt(p_map)==-1)

perror(" detach error ");

? return 0;

?}

读共享内存示例二

? #include <sys/ipc.h>

? #include <sys/shm.h>

? #include <sys/types.h>

? #include <unistd.h>

? typedef struct{

?

char name[4];

?

int age;

? } people;

? main(int argc, char** argv)

?{

?

int shm_id, i;

?

key_t key;

?

people *p_map;

?

char* name = "/usr/local/2013_autumn";

?

key = ftok(name,0);

?

if(key == -1) perror("ftok error");

?

shm_id = shmget(key,4096,IPC_CREAT);

?

if(shm_id == -1)

?

{

?

perror("shmget error");

?

return;

?

}

?

p_map = (people*)shmat(shm_id,NULL,0);

?

for(i = 0;i<10;i++)

?

{

?

printf( "name:%s\n",(*(p_map+i)).name );

?

printf( "age %d\n",(*(p_map+i)).age );

?

}

?

if(shmdt(p_map) == -1)

?

perror(" detach error ");

?

return 0;

?}

主要内容
? 传统进程通信
– 信号通信 – 管道通信
? System V IPC进程通信
– 消息队列 – 共享主存 – 信号量
98

IPC信号量
? 信号量是具有整数值的对象,表示可用资源的数量(IPC信号量资源) ? System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量
的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使 用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控 制的需要; ? 信号量类型
? 二值信号量:信号量的值只能取0或1 ? 通用信号量:信号量的值可以取任意非负整型值 ? IPC信号量与其他进程间通信方式不同: – 它本身不具备数据交换的功能,而是通过控制其它通信资源来实现进 程间通信。 – 信号量本身不具备数据传输的功能,只是一种外部资源的标识。通过 该标识可以判断外部资源是否可用,信号量在此过程中负责数据操作 的互斥,同步。
99

信号量相关数据结构
? (1)系统中每个信号量的数据结构(sem) ? struct sem {
unsigned short semval; /* 信号量的值 */ pid_t sempid; /*在信号量上最后一次操 作的进程号 * ..... ? };

? (2)系统中表示信号量集合(set)的数据结构(semid_ds)

? struct semid_ds {

?

struct ipc_perm sem_perm; /* IPC权限 */

?

long sem_otime; /* 最后一次对信号量操作(semop)的时间

*/

?

long sem_ctime; /* 对这个结构最后一次修改的时间 */

?

struct sem *sem_base; /* 在信号量数组中指向第一个信号量

的指针 */

?

struct sem_queue *sem_pending; /* 待处理的挂起操作*/

?

struct sem_queue **sem_pending_last; /* 最后一个挂起操作

*/

?

struct sem_undo *undo;/* 在这个数组上的undo 请求 */

?

ushort sem_nsems; /* 在信号量数组上的信号量编号 */

?

};

操作信号量
? 1、 打开或创建信号量 与消息队列的创建及打开基本相同。
? 2、 信号量值操作 linux可以增加或减小信号量的值,相应于对共享资源的释放和 占有。
? 3、 获得或设置信号量属性:
? system V信号量API只有三个,使用时需要包括几个头文件:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h>

信号量的基本操作—semget()
? 功能 – 创建一个新信号量集或取得一个已有信号量集的标示符
? 函数原型 – int semget(key_t key, int nsems, int flag)
? 参数说明 – nsems:需要创建的信号量数目, – 最大值定义:#define SEMMSL 250[Linux/sem.h],若打开一个现有 信号量集合,则nsems通常设置为0; – Flag:可以取IPC_CREAT,表示创建一个新的唯一的信号量。如果取 IPC_EXCL | IPC_CREAT,表示创建一个新的信号量,如果信号量已 存在,则返回一个错误。
? 返回值 – 调用成功返回信号量集描述符 – 失败时返回-1
103

信号量的基本操作—semop()

? 功能: – 改变信号量的值 。
? 函数原型:
– int semop(int semid, struct sembuf semoparry[], unsigned nsops); ? 参数说明:
– semid: 由semget返回的信号量集标示符 – semoparry: 指向类型为struct sembuf的数组 – nsops: 对信号量操作的次数 ? 返回值: – 成功返回0;失败返回返回-1。

? struct sembuf {

unsigned short sem_num;

/* semaphore index in array */

short

sem_op;

/* semaphore operation */

short

sem_flg;

/* operation flags */

? };

104

sembuf结构
– sem_num:信号量在集合中的编号; – sem_op:信号量在一次操作中需要改变的数值。
? >0:释放相应数量的资源,将sem_op的值累加到信号量的值上; ? =0:表示调用进程希望等待该信号量值变为0,用于对共享资源是否已用完
的测试;
如果此时信号量的值=0,则函数立即返回; 如果此时信号量的值!=0,则根据参数sem_flg做出决定;
当sem_flg = IPC_NOWAIT时,semop函数出错返回EAGAIN; 当sem_flg != IPC_NOWAIT时,调用进程阻塞,直到当前信号量的值==0. ? <0:进程要申请|sem_op|个共享资源; ? 如果此时信号量的值>=|sem_op|,则从当前信号量中减去|sem_op|; ? 如果此时信号量的值< |sem_op|,则根据参数sem_flg做出决定; 当sem_flg = IPC_NOWAIT时,semop函数出错返回EAGAIN; 当sem_flg != IPC_NOWAIT时,调用进程阻塞,直到当前信号量的值>= |sem_op|。 – sem_flg:说明函数semop的行为。 ? IPC_NOWAIT:当期望的操作无法完成时,semop函数出错返回EAGAIN; ? SEM_UNDO:当进程终止时,由内核释放该进程占有的信号量
105

信号量的基本操作—semctl()

? 功能:对信号量进行控制,可用于删除一个信号量 。

? 函数原型

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

? 参数说明

– semid由semget返回的信号量集标示符;

– semnum:信号量集semid中的某一个信号量的索引;

– cmd:对编号为semnum的信号量执行的操作命令;

– arg:用于设置或返回信号量信息。(根据第三个参数而改变)

? 返回值:调用成功返回0;失败时返回-1

? union semun{

int val;

// for SETVAL

struct semid_ds *buf; //for IPC_STAT and IPC_SET

unsigned short *array; //for GETALL and SETALL

}

106

信号量的基本操作—semctl(续)
? cmd命令类型 – IPC_STAT:将信号量集的信息复制到arg 参数,此时第二个参 数无用; – IPC_SET:与上一个操作相反 – IPC_RMID:删除信号量,不使用第四个参数 – GETALL/SETALL:获取/设置所有信号量的值 – GETVAL/SETVAL:获得/设置指定信号量的值 – GETNCNT:返回等待semnum所代表信号量的值增加的进程数 – GETPID:返回最后一个对semnum所代表信号量执行semop操 作的进程ID – GETZCNT:返回等待semnum所代表信号量的值变成0的进程 数
107

创建信号量集

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/sem.h>

? int main()

?{

?

int sem_id;

?

int nsems = 1;

?

struct sembuf buf;

?

system("ipcs -s");

?

sem_id = semget(IPC_PRIVATE,

nsems, 0666);

?

if(sem_id < 0){

?

perror("semget");

?

exit(1);

?

}

? printf("successfully created a semaphore:%d\n",sem_id);

?

buf.sem_num = 0;

?

buf.sem_op = 1;

?

buf.sem_flg = IPC_NOWAIT;

?

if(semop(sem_id, &buf,

nsems)<0)

?

{

?

perror("semop ");

?

exit(1);

?

}

?

system("ipcs -s");

?

exit(0);

?}

信号量操作示例一

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/sem.h>

? #define SEMKEY 12345

? #define PERMS 0666

? struct sembuf op_up[1] = {0,1,0};

? struct sembuf op_down[1] = {0,-1,0};

? int semid = 0;

? int res=0;

? void seminit(){

?

semid = semget(SEMKEY, 1, IPC_CREAT | IPC_EXCL);

?

if(semid < 0) {

?

printf("semget failed!\n");

?

exit(1);

?

}

?

else { res = semctl(semid, 0, SETVAL, 1); }

?}

? void down()

?{

?

res = semop(semid, &op_down[0], 1);

?}

? void up()

?{

?

res = semop(semid, &op_up[0], 1);

?}

? int main(int argc , char* argv[])

?{

?

seminit();

?

printf("Before critical code!\t");

?

semvalue = semctl(semid, 0, GETVAL);

?

printf("semvalue1:%d\n",semvalue);

?

down();

?

?

printf("Enter critical code!!\n");

?

semvalue = semctl(semid, 0, GETVAL);

?

printf("semvalue:%d\n",semvalue);

?

sleep(3);

?

up();

?

printf("after critical code!!\n");

?

semvalue = semctl(semid, 0, GETVAL);

?

printf("semvalue:%d\n",semvalue);

?

int res=semctl(semid,0,IPC_RMID);

?

printf("remove sem:%d\n",res);

?}

信号量操作示例二

? #include <stdio.h>

? #include <stdlib.h>

? #include <sys/types.h>

? #include <sys/ipc.h>

? #include <sys/sem.h>

? union semun{

?

int val;

/* value for SETVAL */

?

struct semid_ds *buf; /* buffer for IPC_STAT, IPC_SET */

?

unsigned short int *array; /* array for GETALL, SETALL */

?

struct seminfo *__buf; /* buffer for IPC_INFO */

?

};

? int main()

?{

?

int sem_id;

?

int nsems = 2;

?

int semvalue=0;

?

unsigned short returnvalue[2]={0,0};

?

union semun semarg;

?

struct sembuf buf[2];

?

struct sembuf buf2[2];

?

system("ipcs -s");

? sem_id = semget(IPC_PRIVATE, nsems, 0666);

?

if(sem_id < 0){

?

perror("semget");

?

exit(1);

?

}

?

printf("successfully created a semaphore:%d\n",sem_id);

?

#if 0

?

buf.sem_num = 0;

?

buf.sem_op = 1;

?

buf.sem_flg = IPC_NOWAIT;

?

#endif

?

buf[0].sem_num = 0;

?

buf[0].sem_op = 1;

?

buf[0].sem_flg = IPC_NOWAIT;

?

?

buf[1].sem_num = 0;

?

buf[1].sem_op = 4;

?

buf[1].sem_flg = IPC_NOWAIT;

?

? if(semop(sem_id, buf, 2)<0)

?

{

?

perror("semop ");

?

exit(1);

?

}

?

buf2[0].sem_num = 1;

?

buf2[0].sem_op = 1;

?

buf2[0].sem_flg = IPC_NOWAIT | SEM_UNDO;

?

?

buf2[1].sem_num = 1;

?

buf2[1].sem_op = 2;

?

buf2[1].sem_flg = IPC_NOWAIT | SEM_UNDO;

?

?

if(semop(sem_id, buf2, 2)<0)

?

{

?

perror("semop ");

?

exit(1);

?

}

?

#if 0

?

semvalue = semctl(sem_id, 0, GETVAL);

?

printf("semvalue1:%d\n",semvalue);

?

semctl(sem_id, 0, SETVAL,3);

?

semvalue = semctl(sem_id, 0, GETVAL);

?

printf("semvalue2:%d\n",semvalue);

?

semop(sem_id, &buf, 1);

?

semvalue = semctl(sem_id, 0, GETVAL);

?

printf("semvalue3:%d\n",semvalue);

?

#endif

?

semarg.array=returnvalue;

?

semctl(sem_id, 0,GETALL, semarg);

?

printf("returnvalue1:%d returnvalue2:%d\n",semarg.array[0],semarg.array[1]);

?

int semValue1 = semctl(sem_id, 0, GETVAL);

?

int semValue2 = semctl(sem_id, 1, GETVAL);

?

printf("semValue1:%d\tsemValue2:%d\n",semValue1,semValue2);

?

semctl(sem_id, 0, IPC_RMID);

?

system("ipcs -s");

?

return 0;

?}


相关文档

IPC(进程间通信)
进程通信之IPC
深刻理解Linux进程间通信(IPC)
深刻理解Linux进程间通信(IPC)
3.0进程间通信IPC
Android进程间通信--消息机制及IPC机制实现
进程间通信 IPC interprocess communication
详解Linux进程+进程间通信IPC
chapter06 进程间通信(IPC)
浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路
电脑版