进程间通信
- 无名管道(pipe)
- 有名管道 (fifo)
- 信号(signal)
- 共享内存(mmap)
- 套接字(socket)
- System V IPC
- 共享内存(share memory)
- 消息队列(message queue)
- 信号灯集(semaphore set)
1. 有名管道和无名管道
1.1 无名管道(pipe)

1.1 .1无名管道特点
无名管道具有如下特点:
- 只能用于具有亲缘关系的进程之间的通信
- 单工的通信模式,具有固定的读端和写端
- 无名管道创建时会返回两个文件描述符,分别用于读写管道
1.1.2 无名管道创建 – pipe
#include<unistd.h>
int pipe(int pfd[2]);
- 成功时返回0,失败时返回EOF
- pfd包含两个元素的整形数组,用来保存文件描述符
- pfd[0]用于读管道;pfd[1]用于写管道
1.1.3 无名管道通信

1.1.4 无名管道通信 – 示例
1.1.4.1创建两个子进程,都往无名管道里写数据,父进程读数据
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/wait.h>
int main(){
pid_t pid1, pid2;
int pfd[2];
char buff[32];
int status;
if(pipe(pfd) < 0){
perror("pipe");
return -1;
}
pid1 = fork();
if(pid1 < 0){
perror("pid1");
return -1;
}else if (pid1 == 0){
strcpy(buff,"I am pid1");
write(pfd[1],buff,32);
exit(0);
}else{
pid2 = fork();
if(pid2 < 0){
perror("pid2");
return -1;
}else if(pid2 == 0){
sleep(2);
strcpy(buff,"I am pid2");
write(pfd[1],buff,32);
exit(0);
}else{
wait(&status);
read(pfd[0],buff,32);
printf("%s -- status:%d\n",buff,WEXITSTATUS(status));
wait(&status);
read(pfd[0],buff,32);
printf("%s -- status:%d\n",buff,WEXITSTATUS(status));
}
}
return 0;
}
1.1.4.2 观察无名管道最多能写入多少内容
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<error.h>
#include<sys/wait.h>
#include<string.h>
int main(){
pid_t pid;
int pfd[2];
char buf[32] = {0};
int re;
re = pipe(pfd);
if(re < 0){
perror("pipe");
return -1;
}
printf("pfd[0]=%d,pfd[1]=%d\n",pfd[0],pfd[1]);
pid = fork();
if(pid < 0){
perror("fork");
return -1;
}else if(pid > 0){
int j = 0;
while(1){
strcpy(buf,"hahahaha");
for(int i = 0; i < 1000; i++){
j++;
if(j*strlen(buf)%1024 == 0){
printf("j=%d -- size:%luk\n",j,j*strlen(buf)/1024);
}
write(pfd[1],buf,strlen(buf));
}
sleep(1);
}
}else{
sleep(20);
while(1){
re = read(pfd[0],buf,20);
if(re > 0){
printf("read buf = %s\n",buf);
}else{
printf("re = %d\n",re);
}
}
}
return 0;
}
- 结论是最多写入64K。当读端延时读取的情况下,pipe最多容纳64K内容。
1.1.5 无名管道的读写特性:
- 读管道:
1.管道中有数据,read返回实际读到的字节数。
2.管道中无数据:
(1)管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2)写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu) - 写管道:
1.管道读端全部被关闭,进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2.管道读端没有全部关闭:
(1)管道已满,write阻塞。(管道大小64K)
(2)管道未满,write将数据写入,并返回实际写入的字节数。
1.2 有名管道
- 有名管道可以使非亲缘的两个进程互相通信
- 通过路径名来操作,在文件系统中可见,但内容存放在
内存中 - 文件IO来操作有名管道
- 遵循先进先出规则
- 不支持lseek操作
- 单工读写
1.2.1 有名管道创建 – mkfifo
#include<unistd.h>
#include <fcntl.h>
int mkfifo(const char *path, mode_t mode);
- 成功时返回0,失败时返回EOF
- path创建的管道文件路径
- mode 管道文件的权限,如0666
1.2.2 有名管道读写 – 示例
进程A:循环从键盘输入并写入有名管道myfifo,输入quit时退出
进程B:循环统计进程A每次写入myfifo的字符串的长度
写端:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(){
int re, fd;
char buf[32];
re = mkfifo("myfifo",0666);
if(re < 0){
perror("mkfifo");
// return -1;
}
fd = open("myfifo",O_WRONLY);
if(fd < 0){
perror("open");
return -1;
}
printf("after open\n");
while(1){
fgets(buf,32,stdin);
write(fd,buf,strlen(buf));
}
}
读端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
int main(){
int re,fd;
char buf[32];
fd = open("myfifo",O_RDONLY);
if(fd < 0){
perror("open");
return -1;
}
printf("after open read\n");
while(1){
re = read(fd,buf,32);
if(re > 0){
printf("read fifo=%s\n",buf);
}else{
printf("re = %d\n",re);
sleep(1);
}
}
return 0;
}
- 注意事项:
1.就是程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递
2.第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的
3.对于以只读方式(O_RDONLY)打开的FIFO文件,如果open
调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
4.对于以只写方式(O_WRONLY)打开的FIFO文件,如果ope调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回。
2. 共享内存(内存映射的使用、注意事项、进程间通信、systemV共享内存)
2.1 内存映射概念
- 共享内存可以通过mmap()映射普通文件。
- 使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。
- mmap的优点:实现了用户空间和内核空间的高效交互方式


函数定义:
void *mmap(void *addr, size_t length, int prot, int flags, int fd,off_t offset);
功能:创建共享内存映射
函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED(((void *) -1)),设置errno值
参数说明:
addr:指定要映射的内存地址,一般设置为NULL让操作系统自动选择合适的内存地址。
length:必须>0。映射地址空间的字节数,它从被映射文件开头offset个字节开始算起。
prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读) PROT_WRITE(可写)
, PROT_EXEC(可执行), PROT_NONE(不可访问)。
flags:由以下几个常值指定:MAP_SHARED(共享的)MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用start参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED ,MAP_PRIVATE必选其一,而MAP_FIXED则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)
fd:表示要映射的文件句柄。如果匿名映射写-1。
offset:表示映射文件的偏移量,一般设置为0表示从文件头部开始映射。
注意事项:
(1)创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。
(2)当MAP_SHARED时,要求:映射区的权限应<=文件打开的权限(出于对映射区的保护),如果不满足报非法参数 (Invalid argument)错误。当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。
(3)映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。
(4)用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)
(5)文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)
(6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误,超出映射的内存大小报段错误
(7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
mmap()映射的种类:
1.基于文件的映射
2.匿名映射适用于具有亲缘关系的进程之间.释放内存映射
munmap函数
int munmap(void *addr, size_t length);
返回值:成功返回0,失败返回-1,并设置errno值。
函数参数:
addr:调用mmap函数成功返回的映射区首地址
length:映射区大小(即:mmap函数的第二个参数)
2.1.2 内存映射案例
往映射区写数据
#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main(){
void *addr;
int fd;
fd = open("test",O_RDWR);
if(fd < 0){
perror("open");
return -1;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(addr == MAP_FAILED){
perror("mmap");
return -1;
}
close(fd);
int i = 0;
while(i < 2048){
memcpy((addr + i),"a",1);
sleep(1);
i++;
}
return 0;
}
从映射区读数据
#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
int main(){
void *addr;
int fd;
fd = open("test",O_RDWR);
if(fd < 0){
perror("open");
return -1;
}
int len = lseek(fd,0,SEEK_END);
addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(addr == MAP_FAILED){
perror("mmap");
return -1;
}
close(fd);
while(1){
printf("read=%s\n",(char *)addr);
sleep(1);
}
return 0;
}
匿名映射
#include<sys/mman.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/wait.h>
int main(){
void *addr;
addr = mmap(NULL,2048,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
if(addr == MAP_FAILED){
perror("mmap");
return -1;
}
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
return -1;
}else if(pid > 0){
memcpy(addr,"1234567890",10);
wait(NULL);
}else{
sleep(1);
printf("read=%s\n",(char *)addr);
}
munmap(addr,2048);
return 0;
}
2.2 共享内存(sytem V)
共享内存使用步骤
– ftok函数创建Key
– 创建/打开共享内存
– 映射共享内存
– 共享内存读写
– 共享内存控制System V IPC
– IPC 对象包含: 共享内存、消息队列和信号灯集
– 每个IPC对象有唯一的ID 用Key关联
– IPC对象创建后一直存在,直到被显式地删除
– ipcs / ipcrm共享内存
– 共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
– 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
– 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

ftok函数
#include<sys/types.h>
#include <sys/ipc.h>
key_tftok(const char *path,int proj_id);
– 成功时返回合法的key值,失败时返回EOF
– path存在且可访问的文件的路径
– proj_id用于生成key的数字,范围1-255。共享内存创建 – shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmflg);
– 成功时返回共享内存的id,失败时返回EOF
– key和共享内存关联的key,PC_PRIVATE 或 ftok生成
– shmflg 共享内存标志位IPC_CREAT|0666共享内存映射 – shmat
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
– 成功时返回映射后的地址,失败时返回(void *)-1
– shmid要映射的共享内存id
– shmaddr映射后的地址, NULL表示由系统自动映射
– shmflg标志位 0表示可读写;SHM_RDONLY表示只读共享内存撤销映射 – shmdt
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void *shmaddr);
– 成功时返回0,失败时返回EOF
– 不使用共享内存时应撤销映射
– 进程结束时自动撤销共享内存控制 – shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
– 成功时返回0,失败时返回EOF
– shmid要操作的共享内存的id
– cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMID
– buf保存或设置共享内存属性的地址
2.2.1 共享内存案例
往共享内存写数据
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key < 0){
perror("ftok");
return -1;
}
printf("key=%x\n",key);
shmid = shmget(key,512,IPC_CREAT|0666);
if(shmid<0){
perror("shmget\n");
return -1;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return -1;
}
strcpy(buf,"hello world");
return 0;
}
从共享内存读数据
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
int main(){
key_t key;
int shmid;
char *buf;
key = ftok("keytest",100);
if(key < 0){
perror("ftok");
return -1;
}
printf("key=%x\n",key);
shmid = shmget(key,512,0666);
if(shmid<0){
perror("shmget\n");
return -1;
}
printf("shmid=%d\n",shmid);
buf = shmat(shmid,NULL,0);
if(buf<0){
perror("shmat");
return -1;
}
printf("share mem=%s\n",buf);
shmdt(buf);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
3. 信号机制
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式linux内核通过信号通知用户进程,不同的信号类型代表不同的事件。
Linux对早期的unix信号机制进行了扩展
进程对信号有不同的响应方式
– 缺省方式
– 忽略信号
– 捕捉信号
- 信号的产生
– 按键产生
– 系统调用函数产生(比如raise,kill)
– 硬件异常
– 命令行产生 (kill)
– 软件条件(比如被0除,访问非法内存等)
3.1 常用信号


3.1.1 信号相关命令
kill [-signal] pid
默认发送SIGTERM
-sig 可指定信号
pid指定发送对象killall [-u user | prog]
prog指定进程名
user指定用户名kill -l
查看所有信号类型
3.1.2 信号发送 – kill / raise
#include<unistd.h>
#include <signal.h>
int kill(pid_t pid,int sig);
int raise(int sig);//给自己发信号
– 成功时返回0,失败时返回EOF
– pid 接收进程的进程号:0代表同组进程; -1代表所有进程
– sig 信号类型
#include<signal.h>
#include<unistd.h>
int main(){
//kill(15090,11);
raise(11);
return 0;
}
3.1.3 信号相关函数 – alarm / pause
int alarm(unsigned int seconds);
– 成功时返回上个定时器的剩余时间,失败时返回EOF
– seconds 定时器的时间
– 一个进程中只能设定一个定时器,时间到时产生SIGALRM
int pause(void);
– 进程一直阻塞,直到被信号中断
– 被信号中断后返回-1,errno为EINTR
#include<signal.h>
#include<unistd.h>
#include<errno.h>
#include<stdio.h>
int main(){
//kill(15090,11);
//raise(11);
int ret = alarm(5);
printf("ret1 = %d\n",ret);
if(ret == EOF){
perror("alarm");
return errno;
}
ret =pause();
printf("ret2 = %d\n",ret);
return 0;
}
3.1.4 定时器函数
ualarm(循环发送)
useconds_t ualarm(useconds_t usecs, useconds_t interval);
以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生
int setitimer(int which, const struct itimerval *new_value, struct itimerval old_value);
功能:定时的发送alarm信号
参数:
which:
ITIMER_REAL:以逝去时间递减。发送SIGALRM信号
ITIMER_VIRTUAL:计算进程(用户模式)执行的时间。发送SIGVTALRM信号
ITIMER_PROF:进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间。发送SIGPROF信号
new_value:负责设定timout时间
old_value:存放旧的timeout值,一般指定为NULL
struct itimerval {
struct timeval it_interval;//闹钟触发周期
struct timeval it_value;//闹钟触发时间
};
struct timeval {
time_t tv_sec; / seconds /
suseconds_t tv_usec; / microseconds */
};
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
void handle(int sig){
printf("timer sig=%d\n",sig);
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
struct itimerval timevalue;
timevalue.it_interval.tv_sec = 1;
timevalue.it_interval.tv_usec = 0;
timevalue.it_value.tv_sec = 5;
timevalue.it_value.tv_usec = 0;
setitimer(ITIMER_REAL,&timevalue,NULL);
sigaction(SIGALRM,&act,NULL);
while(1){
sleep(1);
}
return 0;
}
3.2 设置信号响应方式 – signal
#include<unistd.h>
#include <signal.h>
void (*signal(int signo, void (*handler)(int)))(int);
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
– 成功时返回原先的信号处理函数,失败时返回SIG_ERR
– signo 要设置的信号类型
– handler 指定的信号处理函数: SIG_DFL代表缺省方式;SIG_IGN 代表忽略信号;
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<string.h>
#include<linux/posix_types.h>
void handle(int sig){
printf("I cath the sig = %d\n",sig);
}
int main(){
signal(SIGINT,handle);
while(1){
sleep(1);
}
return 0;
}
3.2.1 sigaction函数:
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;
void (sa_restorer)(void);
}
参数:signum:处理的信号
act,oldact:处理信号的新行为和旧的行为,是一个sigaction结构体。
sigaction结构体成员定义如下:
sa_handler:是一个函数指针,其含义与signal函数中的信号处理函数类似
sa_sigaction:另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。
sa_flags参考值如下:
SA_SIGINFO:使用sa_sigaction成员而不是sa_handler作为信号处理函数
SA_RESTART:使被信号打断的系统调用自动重新发起。
SA_RESETHAND:信号处理之后重新设置为默认的处理方式。
SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。
re_restorer:是一个已经废弃的数据域
3.3 使用SIGCHLD信号实现回收子进程
SIGCHLD的产生条件
1.子进程终止时
2.子进程接收到SIGSTOP信号停止时
3.子进程处在停止态,接受到SIGCONT后唤醒时
#include<stdio.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
void handle(int sig){
wait(NULL);
printf("get sig = %d\n",sig);
}
int main(){
pid_t pid;
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
pid = fork();
if(pid > 0){
sigaction(SIGCHLD,&act,NULL);
while(1){
printf("this is father proccess\n");
sleep(1);
}
}else if(pid == 0){
sleep(5);
exit(0);
}
return 0;
}
3.4 信号机制下(阻塞、信号集、信号驱动任务)
3.4.1 信号集、信号的阻塞
- 有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。
- 信号的阻塞概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的状态:
信号递达(Delivery):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获)
信号未决(Pending):从产生到递达之间的状态
3.4.2 信号集操作函数
– sigset_t set;自定义信号集。是一个32bit 64bit 128bit 的数组。
– sigemptyset(sigset_t *set); 清空信号集
– sigfillset(sigset_t *set);全部置1
– sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中
– sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除
– sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。设定对信号集内的信号的处理方式(阻塞或不阻塞)
#include <signal.h>
– int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset);
– 返回值:若成功则返回0,若出错则返回-1
– 首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
– 其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
– how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)
– SIG_BLOCK:把参数set中的信号添加到信号屏蔽字中
– SIG_UNBLOCK:从信号屏蔽字中删除参数set中的信号
– SIG_SETMASK:把信号屏蔽字设置为参数set中的信号
– int pause(void);进程一直阻塞,直到被信号中断,返回值:-1并设置errno为EINTR
函数行为:
1.如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。
2.如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回
3.如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。
4.pause收到的信号如果被屏蔽,那么pause就不能被唤醒
– int sigsuspend(const sigset_t *sigmask);
– 功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行
– 参数:sigmask:希望屏蔽的信号
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
printf("I get sig = %d\n",sig);
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigset_t set;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigprocmask(SIG_BLOCK,&set,NULL);
sleep(5);
sigprocmask(SIG_UNBLOCK,&set,NULL);
while(1){
sleep(1);
}
return 0;
}
*SIGINT信号被屏蔽等5s之后解屏蔽,然后信号抵达,执行捕获函数
3.4.3 信号驱动任务
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
printf("this is sig = %d\n",sig);
}
void doTask(){
printf("task start\n");
sleep(3);
printf("task end\n");
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGHUP,&act,NULL);
sigset_t set, set2;
sigemptyset(&set);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGHUP);
pause();
while(1){
doTask();
pause();
}
return 0;
}

- 我们发现在执行任务期间再来信号,会打断任务的处理。因此需要添加屏蔽信号
解决方法如下:
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
printf("this is sig = %d\n",sig);
}
void doTask(){
printf("task start\n");
sleep(3);
printf("task end\n");
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGHUP,&act,NULL);
sigset_t set, set2;
sigemptyset(&set2);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGHUP);
pause();
while(1){
sigprocmask(SIG_BLOCK,&set,NULL);
doTask();
sigprocmask(SIG_UNBLOCK,&set,NULL);
pause();
}
return 0;
}

- 但是又出现了新问题,虽然在执行任务期间,信号被堵塞,信号被解除堵塞之后马上抵达执行捕获函数,但是没有被pause收到,因为此时pause还没执行。所以进程马上又被堵塞,无法进行下次任务。
解决方法如下:
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handle(int sig){
printf("this is sig = %d\n",sig);
}
void doTask(){
printf("task start\n");
sleep(3);
printf("task end\n");
}
int main(){
struct sigaction act;
act.sa_handler = handle;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
sigaction(SIGHUP,&act,NULL);
sigset_t set, set2;
sigemptyset(&set2);
sigaddset(&set,SIGINT);
sigaddset(&set,SIGHUP);
pause();
while(1){
sigprocmask(SIG_BLOCK,&set,NULL);
doTask();
sigsuspend(&set2);
// sigprocmask(SIG_UNBLOCK,&set,NULL);
// pause();
}
return 0;
}
- sigsuspend相当于 sigprocmask(SIG_UNBLOCK,&set,NULL)和pause()的原子操作。
4. 信号灯
4.1 信号量/灯(semaphore)
概念:是不同进程间或一个给定进程内部不同线程间同步的机制。类似我们的PV操作概念:
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
初始化
P操作(申请资源)
V操作(释放资源)
4.2 信号量-P/V操作
P(S)含义如下:
if(信号量的值大于0) {
申请资源的任务继续运行;
信号量的值减一;
}else {
申请资源的任务阻塞;
}
V(S)含义如下:
信号量的值加一;
if (有任务在等待资源) {
唤醒等待的任务,让其继续运行
}
4.3 Posix信号量
- posix中定义了两类信号量:
– 无名信号量(基于内存的信号量,linux仅支持线程同步)
– 有名信号量

4.4 信号灯相关函数
4.4.1 有名信号灯
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数:
– name:name是给信号灯起的名字
– oflag:打开方式,常用O_CREAT
– mode:文件权限。常用0666
– value:信号量值。二元信号灯值为1,普通表示资源数目
– 信号灯文件位置:/dev/shm
- 有名信号灯关闭
int sem_close(sem_t *sem); - 有名信号灯的删除
int sem_unlink(const char* name);
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/shm.h>
#include<semaphore.h>
#include<stdlib.h>
#include<fcntl.h>
int shmid;
char *shmadrr;
void delsemfile(int sig)
{
sem_unlink("mysem_w");
shmdt(shmadrr);
shmctl(shmid,IPC_RMID,NULL);
exit(0);
}
int main(){
sem_t *sem_w, *sem_r;
key_t key;
struct sigaction act;
act.sa_handler = delsemfile;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key < 0){
perror("ftok");
return -1;
}
shmid = shmget(key, 500, IPC_CREAT|0666);
if(shmid < 0){
perror("shmget");
return -1;
}
shmadrr = shmat(shmid,NULL, 0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR, 0666, 0);
while(1){
sem_wait(sem_w);
printf(">");
fgets(shmadrr,500, stdin);
sem_post(sem_r);
}
return 0;
}
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
#include<sys/shm.h>
#include<semaphore.h>
#include<stdlib.h>
#include<fcntl.h>
int shmid;
char *shmadrr;
void delsemfile(int sig){
shmdt(shmadrr);
shmctl(shmid,IPC_RMID,NULL);
sem_unlink("mysem_r");
exit(0);
}
int main(){
sem_t *sem_w, *sem_r;
key_t key;
struct sigaction act;
act.sa_handler = delsemfile;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key < 0){
perror("ftok");
return -1;
}
shmid = shmget(key, 500, 0666);
if(shmid < 0){
perror("shmget");
return -1;
}
shmadrr = shmat(shmid,NULL, 0);
sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);
sem_r = sem_open("mysem_r",O_CREAT|O_RDWR, 0666, 0);
while(1){
sem_wait(sem_r);
printf("%s",(char *)shmadrr);
sem_post(sem_w);
}
return 0;
}
4.4.2 无名信号量
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int val);
成功时返回0,失败时EOF
sem 指向要初始化的信号量对象
pshared 0 – 线程间 1 – 进程间
val信号量初值
无名信号灯销毁
int sem_destroy(sem_t* sem);
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>
#include<fcntl.h>
#include<pthread.h>
#include<sys/shm.h>
#include<semaphore.h>
sem_t sem_w,sem_r;
int shmid;
char *shmadrr;
void destorysem(int sig){
shmdt(shmadrr);
shmctl(shmid,IPC_RMID,NULL);
sem_destroy(&sem_w);
sem_destroy(&sem_r);
exit(0);
}
void *readmem(void *arg){
pthread_detach(pthread_self());
while(1){
sem_wait(&sem_r);
printf("%s",shmadrr);
sem_post(&sem_w);
}
pthread_exit(NULL);
}
int main(){
key_t key;
struct sigaction act;
act.sa_handler = destorysem;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key < 0){
perror("ftok");
return -1;
}
shmid = shmget(key,500,IPC_CREAT|0666);
if(shmid < 0){
perror("shmget");
return -1;
}
shmadrr = shmat(shmid,NULL,0);
sem_init(&sem_w,0,1);
sem_init(&sem_r,0,0);
pthread_t tid;
pthread_create(&tid,NULL,readmem,NULL);
while(1){
sem_wait(&sem_w);
printf(">");
fgets(shmadrr,500,stdin);
sem_post(&sem_r);
}
return 0;
}
4.4.3 信号量 – P / V 操作
#include<semaphore.h>
int sem_wait(sem_t *sem);
- P操作
int sem_post(sem_t *sem); - V操作
– 成功时返回0,失败时返回EOF
– sem指向要操作的信号量对象
4.5 System V IPC - 信号灯特点
- System V 信号灯是一个或多个计数信号灯的集
合 - 可同时操作集合中的多个信号灯
- 申请多个资源时避免死锁
4.5.1 System V信号灯使用步骤
打开/创建信号灯 semget
int semget(key_t key, int nsems, int semflg);
– key:ftok产生的key值(和信号灯关联的key值)
– nsems:信号灯集中包含的信号灯数目
– semflg:信号灯集的访问权限,通常为IPC_CREAT |0666
– 返回值:成功:信号灯集ID;失败:-1信号灯初始化 semctl
int semctl ( int semid, int semnum, int cmd…/union semun arg/);
功能:信号灯集合的控制(初始化/删除)
参数:
– semid:信号灯集ID
– semnum:要操作的集合中的信号灯编号
– cmd:
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,需要用到第四个参数:共用体
IPC_RMID:从系统中删除信号灯集合
返回值:成功0;失败-1P/V操作 semop
int semop ( int semid, struct sembuf *opsptr, size_t nops);
功能:对信号灯集合中的信号量进行P - V操作
参数:
– semid:信号灯集ID
– struct sembuf {
short sem_num; //要操作的信号灯的编号
short sem_op;// 1 :释放资源,V操作// -1 :分配资源,P操作
short sem_flg;// 0(阻塞),IPC_NOWAIT, SEM_UNDO
};//对某一个信号灯的操作,如果同时对多个操作,则需要定义这种结构体数组
– nops:要操作的信号灯的个数,1个返回值:成功:0;失败:-1删除信号灯 semctl
#include<semaphore.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<signal.h>
#define SEM_READ 0
#define SEM_WRITE 1
union semun{
int val;
};
void Poperation(int semid,int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = -1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
void Voperation(int semid, int semindex){
struct sembuf sbuf;
sbuf.sem_num = semindex;
sbuf.sem_op = 1;
sbuf.sem_flg = 0;
semop(semid,&sbuf,1);
}
char *shmaddr;
int semid, shmid;
void removesem(int sig){
shmdt(shmaddr);
shmctl(shmid,IPC_RMID,NULL);
semctl(semid,SEM_READ,IPC_RMID);
semctl(semid,SEM_WRITE,IPC_RMID);
exit(0);
}
int main(){
key_t key;
struct sigaction act;
act.sa_handler = removesem;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGINT,&act,NULL);
key = ftok(".",100);
if(key < 0){
perror("ftok");
return -1;
}
semid = semget(key,2,IPC_CREAT|0666);
if(semid < 0){
perror("semget");
return -1;
}
shmid = shmget(key,500,IPC_CREAT|0666);
if(shmid < 0){
perror("shmget");
return -1;
}
shmaddr = shmat(shmid,NULL,0);
union semun mysem;
mysem.val = 0;
semctl(semid,SEM_READ,SETVAL,mysem);
mysem.val =1;
semctl(semid,SEM_WRITE,SETVAL,mysem);
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork");
shmctl(shmid,IPC_RMID,NULL);
semctl(semid,0,IPC_RMID);
exit(-1);
}else if(pid == 0){
while(1){
Poperation(semid,SEM_READ);
printf("%s",shmaddr);
Voperation(semid,SEM_WRITE);
}
}else{
while(1){
Poperation(semid,SEM_WRITE);
printf(">");
fgets(shmaddr,32,stdin);
Voperation(semid,SEM_READ);
}
}
return 0;
}
