概述
信号
- 信号是 Linux 进程间通信的最古老的方式,是软中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式
- 信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件
信号的编号
- 每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
- 信号定义在signal.h头文件中,信号名都定义为正整数,信号名定义路径:/usr/include/i386-linux-gnu/bits/signum.h
- 具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用
- 编号为 1 ~ 31 的信号为传统 UNIX 支持的信号,是不可靠信号(非实时的),编号为 32 ~ 63 的信号是后来扩充的,称做可靠信号(实时信号)
- 不可靠信号和可靠信号的区别在于前者不支持排队,可能会造成信号丢失,而后者不会。非可靠信号一般都有确定的用途及含义, 可靠信号则可以让用户自定义使用。
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
不可靠信号简介:
编号 | 信号 | 作用 |
---|
1 | SIGHUP | 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一 session 内的各个作业, 这时它们与控制终端不再关联。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 | 2 | SIGINT | 程序终止( interrupt )信号, 在用户键入 INTR 字符(通常是 Ctrl + C )时发出,用于通知前台进程组终止进程。 | 3 | SIGQUIT | 和 SIGINT 类似, 但由 QUIT 字符(通常是 Ctrl + / )来控制. 进程在因收到 SIGQUIT 退出时会产生 core 文件, 在这个意义上类似于一个程序错误信号。 | 4 | SIGILL | 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。 | 5 | SIGTRAP | 由断点指令或其它 trap 指令产生. 由d ebugger 使用。 | 6 | SIGABRT | 调用 abort 函数生成的信号。 | 7 | SIGBUS | 非法地址, 包括内存地址对齐( alignment )出错。比如访问一个四个字长的整数, 但其地址不是 4 的倍数。它与 SIGSEGV 的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。 | 8 | SIGFPE | 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。 | 9 | SIGKILL | 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。 | 10 | SIGUSR1 | 留给用户使用 | 11 | SIGSEGV | 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。 | 12 | SIGUSR2 | 留给用户使用 | 13 | SIGPIPE | 管道破裂。这个信号通常在进程间通信产生,比如采用 FIFO (管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到 SIGPIPE 信号。此外用Socket 通信的两个进程,写进程在写 Socket 的时候,读进程已经终止。 | 14 | SIGALRM | 时钟定时信号, 计算的是实际的时间或时钟时间。alarm 函数使用该信号。 | 15 | SIGTERM | 程序结束( terminate )信号, 与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell 命令 kill 缺省产生这个信号。如果进程终止不了,我们才会尝试 SIGKILL。 | 16 | SIGSTKFLT | Linux专用,数学协处理器的栈异常 | 17 | SIGCHLD | 子进程结束时, 父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待( wait )子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略 SIGCHILD 信号,或者捕捉它,或者 wait 它派生的子进程,或者父进程先终止,这时子进程的终止自动由 init 进程来接管)。 | 18 | SIGCONT | 让一个停止( stopped )的进程继续执行。本信号不能被阻塞。可以用一个 handler 来让程序在由 stopped 状态变为继续执行时完成特定的工作。例如, 重新显示提示符。 | 19 | SIGSTOP | 停止( stopped )进程的执行。注意它和 terminate 以及 interrupt 的区别:该进程还未结束, 只是暂停执行。本信号不能被阻塞,处理或忽略。 | 20 | SIGTSTP | 停止进程的运行, 但该信号可以被处理和忽略。用户键入 SUSP 字符时(通常是 Ctrl + Z )发出这个信号。 | 21 | SIGTTIN | 当后台作业要从用户终端读数据时,该作业中的所有进程会收到 SIGTTIN 信号。缺省时这些进程会停止执行。 | 22 | SIGTTOU | 类似于 SIGTTIN,但在写终端(或修改终端模式)时收到。 | 23 | SIGURG | 有“紧急”数据或 out-of-band 数据到达 socket 时产生。 | 24 | SIGXCPU | 超过 CPU 时间资源限制。这个限制可以由 getrlimit/setrlimit 来读取/改变。 | 25 | SIGXFSZ | 当进程企图扩大文件以至于超过文件大小资源限制。 | 26 | SIGVTALRM | 虚拟时钟信号。类似于 SIGALRM,但是计算的是该进程占用的 CPU 时间。 | 27 | SIGPROF | 类似于 SIGALRM/SIGVTALRM,但包括该进程用的 CPU 时间以及系统调用的时间。 | 28 | SIGWINCH | 窗口大小改变时发出。 | 29 | SIGIO | 文件描述符准备就绪,可以开始进行输入/输出操作。 | 30 | SIGPWR | Power failure | 31 | SIGSYS | 非法的系统调用。 |
信号产生方式
-
当用户按某些终端键时: 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。 -
硬件异常将产生信号:除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。 -
软件异常将产生信号:当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。 -
调用函数将发送信号 -
运行 kill 命令将发送信号: 此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程,例如使用kill -9 进程号可杀死程序。
信号的处理
- 信号的处理有忽略,捕捉,和默认三种方式
- 忽略信号: 大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
- 例:使用函数signal(SIGINT,SIG_IGN);//将SIGINT信号忽略,函数执行后按下Ctrl + C中断信号时,会忽略它
- 捕捉信号:告诉内核,用户希望如何处理某一种信号。当信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
- 系统默认动作:对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
- 具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。
信号的发送和接收处理
- 信号作为异步通讯的手段可以实现信号的发送和接收处理,以下面两种方式介绍
方式1:kill发送信号,signal注册一个函数处理信号
-
kill函数: #include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
-
功能:给指定进程发送指定信号 -
参数:
- pid > 0: 将信号传送给进程 ID 为pid的进程。
- pid = 0 : 将信号传送给当前进程所在进程组中的所有进程。
- pid = -1 : 将信号传送给系统内所有的进程。
- pid < -1 : 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
- sig : 信号的编号,这里可以填数字编号,也可以填信号的宏定义
-
返回值:成功返回0 ,失败返回-1 -
示例代码kill.c: #include <signal.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int signum = atoi(argv[1]);
int pid = atoi(argv[2]);
char cmd[32] = {0};
sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
return 0;
}
-
signal函数: #include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
-
功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。 -
参数:
- signum:信号的编号,数字编号或宏定义
- handler: 取值有 3 种情况:
- SIG_IGN:忽略该信号
- SIG_DFL:执行系统默认动作
- 信号处理函数名:自定义信号处理函数(处理函数的参数为signum)
-
pause函数等待信号到来: #include <unistd.h>
int pause(void);
-
示例代码signal.c: #include <signal.h>
#include <stdio.h>
#include <unistd.h>
void handler(int signum)
{
printf("get signum:%d\n",signum);
switch(signum){
case 2:printf("SIGINT\n");break;
case 9:printf("SIGKILL\n");break;
case 10:printf("SIGUSR1\n");break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
pause();
pause();
pause();
return 0;
}
-
先执行signal.c,接收信号后的显示:
方式2:sigqueue发送信号和数据,sigaction接收
-
sigqueue函数: #include <signal.h>
union sigval {
int sival_int;
void *sival_ptr;
};
int sigqueue(pid_t pid, int sig, const union sigval value);
-
功能:向指定进程发送一个信号和数据 -
参数:
- pid:目标进程的进程号
- sig:信号编号
- value:发送的消息,是一个存放信号附带数据的联合体,附带数据可以是一个整数也可以是一个指针
-
返回值:若成功,返回 0;否则,返回 -1 -
示例代码sigqueue.c: #include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv)
{
int signum = atoi(argv[1]);
int pid = atoi(argv[2]);
union sigval value;
value.sival_int = 666;
sigqueue(pid,signum,value);
printf("done\n");
return 0;
}
-
sigaction函数: #include<signal.h>
int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
};
-
功能:sigaction 可以用来查询或设置信号处理方式。 -
参数:
-
signum:参数指出要捕获的信号类型,也就是信号的编号 -
act:参数指定新的信号处理方式,struct sigaction类型如果不为空说明需要对该信号有新的配置
-
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数 -
sa_sigaction的参数有信号编号num,结构体siginfo,指针,指针为空表示没有数据,指针非空表示有数据,而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息 -
siginfo的成员有很多si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。 siginfo_t {
int si_signo;
int si_errno;
int si_code;
int si_trapno;
pid_t si_pid;
uid_t si_uid;
int si_status;
clock_t si_utime;
clock_t si_stime;
sigval_t si_value;
int si_int;
void *si_ptr;
int si_overrun;
int si_timerid;
void *si_addr;
int si_band;
int si_fd;
}
-
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用: SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针 SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用 SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号 -
oldact:备份,如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复 -
示例代码sigaction.c: #include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void handler(int signum, siginfo_t *info, void *text)
{
printf("signum:%d\n",signum);
if(text != NULL){
printf("get data:%d\n",info->si_int);
printf("sig from:%d\n",info->si_pid);
}
}
int main()
{
struct sigaction act;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
printf("my pid:%d\n",getpid());
sigaction(SIGUSR1,&act,NULL);
pause();
return 0;
}
-
编译执行sigaction.c(信号和数据来自sigqueue.c编译运行):
无名管道(pipe)
- 管道也叫无名管道,它是是 UNIX 系统 IPC(进程间通信) 的最古老形式,所有的 UNIX 系统都支持这种通信机制。
- 父进程调用pipe函数的时候,内核在执行pipe函数的时候就会在内核空间创建一个缓存,这个缓存就是无名管道
无名管道的特点:
1、半双工,数据在同一时刻只能在一个方向上流动。
2、数据只能从管道的一端写入,从另一端读出。
3、写入管道中的数据遵循先入先出的规则。
4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。(一般默认64K)
7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。
8、管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。(没关系的进程得不到管道的文件描述符,子进程的文件描述符是fork到的)
pipe函数
#include <unistd.h>
int pipe(int pipefd[2]);
- 想要实现父子进程相互通信可以创建两个管道
- 读管道:有数据时无论写端有无都会对管道内数据读取;无数据时若写端不存在则read返回0,若某进程有写端则read阻塞
- 写管道:有读端就可以写;若无读端存在则管道破裂,该进程收到SIGPIPE信号(13)
- fork后的半双工管道:
父进程——|pipe|——>子进程示例
-
示例代码pipe.c: #include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
int pid;
char buf[32] = {0};
if(pipe(fd) == -1){
printf("creat pipe fail\n");
}
pid = fork();
if(pid < 0){
printf("creat pid fail\n");
}
else if(pid > 0){
printf("this is father\n");
close(fd[0]);
write(fd[1],"wo nen die!",strlen("wo nen die!"));
sleep(1);
}
else{
printf("this is child\n");
close(fd[1]);
read(fd[0],buf,128);
printf("read father %s\n",buf);
}
return 0;
}
有名管道(FIFO)
- 有名管道也叫命名管道,是在文件系统目录中存在的一个管道文件。
- 管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。
- FIFO不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。
mkfifo
-
可以在终端使用shell指令mkfifo 文件名直接创建一个管道文件 -
使用函数创建: #include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
- 功能:有名管道的创建。
- 参数:
- pathname : 普通的路径名,也就是创建后 FIFO 的名字。
- mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。
- 返回值: 成功返回0 ; 失败返回 -1。(文件存在也返回-1,此时errno = EEXIST)
-
open() 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO -
open() 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO
示例代码
-
发送mkfifo_w.c: #include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){
printf("creat named pipe failed\n");
perror("why");
}
int fd = open("./named pipe",O_WRONLY);
write(fd,"NB 666 My Baby!\n",strlen("NB 666 My Baby!\n"));
close(fd);
return 0;
}
-
接收mkfifo_r.c: #include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int i = 5;
char buf[128] = {0};
if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){
printf("creat named pipe failed\n");
perror("why");
}
int fd = open("./named pipe",O_RDONLY);
while(i--){
read(fd,buf,128);
printf("read from named pipe:\n%s",buf);
sleep(1);
}
close(fd);
return 0;
}
-
接收端显示:
IPC对象
-
IPC 对象包含: 共享内存、消息队列和信号量 -
每个IPC对象有唯一的ID(IPC对象创建的时候由系统分配的一个数字,只有创建IPC对象的进程可以获得ID,别的进程不知道这个ID号) -
IPC对象创建后一直存在,直到被显式地删除 -
每个IPC对象有一个关联的KEY(可以看成IPC对象的一个属性,通过KEY值,可以使不同的进程能够打开同一个IPC对象。创建IPC对象的进程把KEY值和IPC对象关联) -
查看IPC对象 ipcs
- 查看共享内存对象ipcs -m
- 查看消息队列对象ipcs -q
- 查看信号量ipcs -s
-
iprm删除IPC对象 ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
-
用ftok生成键值 #include<sys/types.h>
#include<sys/ipc.h>
key_t ftok(const char *pathname,int proj_id);
- 如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值就为0x26010002
消息队列
-
消息队列是消息的链接表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识 -
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级 -
消息队列独立于发送与接收进程,进程终止时,消息队列中的内容不会被删除 -
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按照消息的类型读取
创建或打开一个消息队列
-
msgget函数: #include <sys/msg.h>
int msgget(key_t key, int msgflg);
-
功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。 -
参数:
- key: IPC对象的key 值
- msgflg: 标识函数的行为及消息队列的权限,其取值如下
- IPC_CREAT:创建消息队列。 I
- PC_EXCL: 检测消息队列是否存在。
- 位或权限位:消息队列位或权限位后可以设置消息队列的访问权限,但可执行权限未使用。
-
返回值: 成功返回消息队列的标识符 ;失败返回-1
消息队列的读写
-
消息队列的数据格式由一个结构体定义,mtext的大小可由用户分配 struct _msg
{
long mtype;
char mtext[128];
};
-
发送消息msgsnd: #include <sys/msg.h>
int msgsnd( int msqid, const void *msgp, size_t msgsz, int msgflg);
-
功能: 将新消息添加到消息队列。 -
参数:
- msqid: 消息队列的标识符。
- msgp: 待发送消息结构体的地址。
- msgsz: 消息正文的字节数。
- msgflg:函数的控制属性,其取值如下:
- 0:msgsnd()调用阻塞直到条件满足为止。
- IPC_NOWAIT: 若消息没有立即发送则调用该函数的进程会立即返回。
-
返回值:成功0;失败-1 -
接收消息msgrcv: #include <sys/msg.h>
ssize_t msgrcv( int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg );
-
控制消息队列对象msgctl: #include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
- 功能:用来对消息队列的基本属性进行控制、修改。
- 参数:
- msqid:消息队列标识符。
- cmd:执行的控制命令(在ipc.h中定义):
- IPC_RMID :删除消息队列。从系统中删除给消息队列以及仍在该队列上的所有数据,这种删除立即生效。仍在使用这一消息队列的其他进程在它们下一次试图对此队列进行操作时,将出错,并返回EIDRM。 此命令只能由如下两种进程执行:
- 1.其有效用户ID等于msg_perm.cuid或msg_perm.guid的进程。
- 2.另一种是具有超级用户特权的进程。
- IPC_SET :设置消息队列的属性。按照buf指向的结构中的值,来设置此队列的msqid_id结构。该命令的执行特权与上一个相同。
- IPC_STAT:读取消息队列的属性。取得此队列的msqid_ds结构,并存放在buf*中。
- IPC_INFO:读取消息队列基本情况。
- buf:队列中的内容,一般为NULL
示例代码
-
A.c: #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf {
long mtype;
char mtext[128];
};
int main()
{
int msqid;
key_t key = ftok(".",2021);
struct msgbuf wrBuf = { 888,"How are you? ^_^ "};
struct msgbuf reBuf;
msqid=msgget(key, IPC_CREAT|0777);
if(msqid == -1){
printf("msgget error");
exit(1);
}
msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0);
msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),999,0);
printf("mtext from B:%s\n",reBuf.mtext);
msgctl(msqid,IPC_RMID,NULL);
return 0;
}
-
B.c #include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct msgbuf {
long mtype;
char mtext[128];
};
int main()
{
int msqid;
struct msgbuf reBuf;
struct msgbuf wrBuf = {999,"666666!"};
key_t key = ftok(".",2021);
msqid=msgget(key, IPC_CREAT|0777) ;
if(msqid == -1){
perror("msgget error");
exit(1);
}
if(msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),888,0) == -1){
perror("msgrcv error");
}else{
printf("mtext from A:%s\n",reBuf.mtext);
}
if(msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0) == -1){
perror("msgsnd error");
}
if(msgctl(msqid,IPC_RMID,NULL) == -1){
perror("msgctl error");
}
return 0;
}
-
显示 B进程:
mtext from A:How are you? ^_^
A进程:
mtext from B:666666!
共享内存
- 共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
- 共享内存在进程空间的映射:
使用共享内存通信的一般步骤
1、创建或者打开共享内存 2、进程A连接(映射)共享内存,写入数据 3、进程A断开 4、进程B连接(映射)共享内存,读取数据 5、进程B断开 6、释放共享内存
- 示意图:
创建共享内存
共享内存的映射和解除映射
-
shmat函数: #include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
-
shmdt函数: #include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
-
功能: 将共享内存和当前进程分离 -
参数:
-
返回值: 成功0 ;失败-1
共享内存操作函数
示例代码
-
写进程shm_w.c #include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",1);
shmid = shmget(key,1024*4,IPC_CREAT|0666);
if(shmid == -1){
printf("shmget NO OK\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
strcpy(shmaddr,"hello world");
sleep(5);
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,0);
printf("quit\n");
return 0;
}
-
读进程shm_r.c #include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int shmid;
char *shmaddr;
key_t key;
key = ftok(".",1);
shmid = shmget(key,1024*4,0);
if(shmid == -1){
printf("shmget no ok\n");
exit(-1);
}
shmaddr = shmat(shmid,0,0);
printf("shmat ok\n");
printf("data : %s",shmaddr);
shmdt(shmaddr);
printf("quit\n");
return 0;
}
|