4. 进程通信
4.1 管道
4.1.1 创建管道pipe()
帮助手册
man 2 pipe
包含头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
函数说明:
pipefd[0]:为管道的读取端
pipe[1]:为管道的写入端。
属于半双工通信,管道大小512*8
参数 | 说明 |
---|---|
pipefd | 管道的文件描述符 |
return | 成功:0 失败:-1,并设置errno |
示例: |
#include <stdio.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
int pipefd[2] = { 0 };
if(pipe(pipefd))
perror("create a pipe error\n");
pid_t pid = fork();
if(pid == 0)
{
// child process
close(pipefd[0]);
write(pipefd[1], "Hello", 5);
}
if(pid > 0)
{
// parent process
char buf[256] = { 0 };
close(pipefd[1]);
// 此时read()会阻塞
int ret = read(pipefd[0], buf, sizeof(buf));
if(ret > 0)
{
// printf("%s\n", buf);
write(STDOUT_FILENO, buf, ret);
printf("\n");
}
}
return 0;
}
4.1.2 获取文件配置值fpathconf()
帮助手册:
man 3 fpathconf
包含头文件:
#include <unistd.h>
函数原型:
long fpathconf(int fd, int name);
参数 | 说明 |
---|---|
fd | 文件描述符 |
name | _PC_PIPE_BUF :计算管道大小 |
return | 成功:大小 失败: -1,并设置errno |
4.1.3 mkfifo()
帮助手册:
man 3 mkfifo
包含头文件:
#include <sys/types.h>
#include <sys/stat.h>
函数原型:
int mkfifo(const char *pathname, mode_t mode);
参数 | 说明 |
---|---|
pathanem | 管道名 |
mode | 管道权限 |
return | 成功:0 失败-1,并设置errno |
示例: |
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
// create a fifo file
int ret = mkfifo("./fifo", 0664);
if (ret < 0)
{
perror("created fifo file faild\n");
}
int fd = open("./fifo", O_RDONLY);
char buf[256] = { 0 };
ret = 0;
// read data from fifo
while(1)
{
ret = read(fd, buf, sizeof(buf));
if(ret > 0)
{
printf("%s", buf);
}
}
close(fd);
return 0;
}
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
int fd = open("./fifo", O_WRONLY);
char buf[256] = { 0 };
int num = 0;
// write data from fifo
while(1)
{
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "fifo%04d\n", num++);
write(fd, buf, strlen(buf));
sleep(1);
if(num == 10) break;
}
close(fd);
return 0;
}
4.2 映射
4.2.1 建立内存映射mmap()
帮助手册:
man 2 mmap
包含头文件:
#include <sys/mman.h>
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
函数说明:
mmap()
用来将某个文件内容映射到内存中,对该内存区域的存取即使直接对该文件内容的读写。
参数 | 说明 |
---|---|
addr | 指向欲对应的内存起始地址,通常设置为NULL |
length | 映射的大小 |
prot | 映射区的保护方式:PROT_EXEC :映射区域可被执行PROT_READ :映射区域可被读取PROT_WRITE :映射区可被写入PROT_NONE :映射区域不能存取 |
flags | 映射区域的特性:MAP_FIXED :如果addr 所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。(不建议使用)MAP_SHARED :对映射区域的写入数据会复制回原文件中,而且允许其他映射该文件的程序共享MAP_PRIVATE :对映射区域的写入会产生一个映射文件的复制,不会写入原文件中MAP_ANONYMOUS :建立匿名映射。此时会忽略参数fd ,不涉及文件,而且映射区域无法和其他进程共享MAP_DENYWRITE :只允许对映射区域的写入操作,其他对文件的写入操作会被拒绝MAP_LOCKED 将映射区域锁定,这表示该区域不会被置换(swap)在调用 mmap() 时必须指定MAP_SHARED 或MAP_PRIvATE |
fd | 欲映射到内存的文件描述符 |
offset | 必须是系统分一大小的整数倍 |
return | 成功:映射区的起始地址 失败:-1,并设置errno |
注:使用MAP_SHARED 则要有PROT_WRITE 以及该文件要能写入 |
4.2.2 删除内存映射munmap()
帮助手册:
man 2 munmap
包含头文件:
#include <sys/mman.h>
函数原型:
int munmap(void *addr, size_t length);
函数说明:
取消参数addr所指的映射内存的起始地址
参数 | 说明 |
---|---|
addr | 需要取消映射的地址 |
length | 欲取消的内存大小 |
return | 成功:0 失败:-1,并设置errno |
示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
int main(int argc, char* argv[])
{
int fd = open(argv[1] , O_RDWR);
char* mem = mmap(NULL, 8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(mem == MAP_FAILED)
{
perror("mmap error");
return -1;
}
strcpy(mem, "helloHELLOhelloHELLO");
munmap(mem, 8);
close(fd);
return 0;
}
mmap实现父子进程间通信:
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 #include <sys/mman.h>
8 #include <sys/wait.h>
9
10 int main(int argc, char* argv[])
11 {
12 int fd = open("mem.txt", O_RDWR);
13 int* mem = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
14 if(mem == MAP_FAILED)
15 {
16 perror("mmap error\n");
17 return -1;
18 }
19 pid_t pid = fork();
20 if(pid == 0)
21 {
22 // child process
23 *mem = 100;
24 printf("\tchild *mem = %d\n", *mem);
25 sleep(3);
26 printf("\tchild *mem = %d\n", *mem);
27 }
28 if(pid > 0)
29 {
30 // parent process
31 sleep(1);
32 printf("parent *mem = %d\n", *mem);
33 *mem = 1001;
34 printf("parent *mem = %d\n", *mem);
35 wait(NULL);
36 }
37 close(fd);
38 munmap(mem, 4);
39 return 0;
40 }
4.3 信号
信号特点:简单,不能携带大量信息,满足特定条件触发
信号状态:
- 产生
- 递达
- 未决
信号的默认处理方式:
- 忽略
- 执行默认操作
- 捕获
信号的四要素:
- 编号
- 事件
- 名称
- 默认处理动作
– 忽略
– 终止
– 终止并产生Core文件
– 暂停
– 继续帮助手册:
man 7 signal
信号的产生:
- 终端按键
– Ctrl+c --> 2)SIGINT(终止/中断) “INT” --> Interrupt
– Ctrl+z --> 20)SIGTSTP(暂停/停止) “TSTP” --> Terminal终端
– Ctrl+\ --> 3)SIGQUIT(退出)- 硬件异常
– 除0操作 --> 8)SIGFPE(浮点数例外) “F” --> float浮点数
– 非法访问内存 --> 11)SIGSEGV(段错误)
– 总线错误 --> 7)SIGBUS- kill()/命令
– kill命令产生信号:kill -SIGKILL pid
– kill() :给指定进程发送指定信号(不一定杀死)
4.3.1 给进程发送信号kill()
帮助手册:
man 2 kill
包含头文件:
#include <sys/types.h>
#include <signal.h>
函数原型:
int kill(pid_t pid, int sig);
参数 | 说明 |
---|---|
pid | pid>0 :进程idpid=0 :被调用进程组的所有进程pid=-1 :有权限的进程pid<-1 :给-pid 组的进程 |
sig | sig=0 :不发送信号,通常用来检测进程是否存在或是否有发送信号的权限sig>0 :给进程发送sig 信号 |
return | 成功:0 失败:-1,并设置errno |
示例: |
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
int main(int argc, char* argv[])
{
int i;
for(i=0;i<5;i++)
{
pid_t pid = fork();
if(pid == 0) break;
}
if(i==2)
{
printf("I will kill parent process\n");
sleep(5);
int ret = kill(getppid(),SIGKILL);
while(1) sleep(1);
}
if(i==5)
{
printf("I am parent process\n");
sleep(3);
}
return 0;
}
4.3.2 给调用者发送信号raise()
帮助手册
man raise
包含头文件
#include <signal.h>
函数原型:
int raise(int sig);
参数 | 说明 |
---|---|
sig | 发送的信号 |
return | 成功:0 失败:非0 |
示例: |
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
int main(int argc, char* argv[])
{
printf("I will die\n");
sleep(3);
raise(SIGKILL);
printf("end...\n");
return 0;
}
运行结果:
4.3.3 异常终止进程abort()
帮助手册
man 3 abort
包含头文件:
#include <stdlib.h>
函数原型:
void abort(void)
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
print("begin...\n");
sleep(2);
abort();
print("end...\n");
return 0;
}
运行结果:
4.3.4 设置信号传送闹钟alarm()
帮助手册:
man alarm
包含头文件:
#include <unistd.h>
函数原型:
unsigned int alarm(unsigned int seconds);
函数说明:
alarm()
用来设置信号SIGALRM(终止进程)
在经过参数seconds
指定的秒数后传送给目前的进程,并将之前剩下的时间返回。
参数 | 说明 |
---|---|
seconds | 如果为0则取消之前的闹钟,任何情况下都会取消之前的闹钟 |
return | 返回任何先前计划的闹钟剩余的秒数,如果没有,则为零 |
示例: |
#include <unistd.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
alarm(6);
while(1);
{
printf("...\n");
sleep(1);
}
return 0;
}
运行结果:
4.3.5 设置一个定时器setitimer()
帮助手册:
man setitimer
包含头文件:
#include <sys/time.h>
函数原型:
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
struct itimerval {
struct timeval it_interval; /* 定时器间隔 */
struct timeval it_value; /* 下次到期时间 */
};
struct timeval {
time_t tv_sec; /* 秒 */
suseconds_t tv_usec; /* 微秒 */
};
参数 | 说明 |
---|---|
which | ITIMER_REAL :根据实际时间进行倒计时,在计时结束发送一个SIGALRM 信号ITIMER_VIRTUAL :根据进程消耗用户模式CPU的时间计时(包括进程中的所有线程消耗的时间),在计时结束产生SIGVTALRM 信号ITIMER_PROF :根据进程消耗所有CPU(用户和系统)的时间计时,计时结束发送SIGPROF 信号 |
new_value | 要设置的闹钟时间 |
old_value | 原闹钟时间 |
return | 成功:0 失败:-1,并设置errno |
示例1: |
#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
struct itimerval new_it = {{0,0},{3,0}};
setitimer(ITIMER_REAL &new_it, NULL);
while(1)
{
printf("...\n");
sleep(1);
}
return 0;
}
运行结果:
示例2:
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <signal.h>
void catch_sig(int sig)
{
printf("catch %d signal\n", sig);
}
int main()
{
signal(SIGALRM, catch_sig); # 产生SIGALRM信号时,处理函数为catch_sig
struct itimerval new_it = {{3,0},{5,0}};
setitimer(ITIMER_REAL, &new_it, NULL);
while(1)
{
printf("...\n");
sleep(1);
}
return 0;
}
运行结果:
4.3.6 信号集处理函数
帮助手册:
man sigemptyset
包含头文件:
#include <signal.h>
函数原型:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
清空信号集sigemptyset()
填充信号集sigfillset()
添加某个信号到信号集sigaddset()
从信号集删除某个信号sigdelset()
是否为集合中的成员sigismember()
4.3.7 设置阻塞或解除阻塞信号集sigprocmask()
帮助手册:
man sigprocmask
包含头文件:
- `#include <signal.h>
函数原型:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
函数说明:
sigprocmask
可以用来改变目前的信号掩码,其操作依据参数how
决定
参数 | 说明 |
---|---|
how | SIG_BLOCK :设置阻塞(当前信号集和set 的并集)SIG_UNBLOCK :解除阻塞从(当前信号集移除set 中的信号)SIG_SETMASK :设置为set |
set | 传入的信号集 |
oldset | 旧的信号集,传出 |
return | 成功:0 失败:-1,并设置errno |
4.3.9 获取未决信号集sigpending()
帮助手册:
man sigpending
包含头文件:
#include <signal.h>
函数原型:
int sigpending(sigset_t *set);
参数 | 说明 |
---|---|
set | 传出参数,当前的未决信号集 |
return | 成功:0 失败:-1,并设置errno |
示例:打印当前进程未决信号集
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
int main()
{
sigset_t pending, sigproc;
sigemptyset(&sigproc); // 清空信号集
sigaddset(&sigproc, SIGINT); // 添加信号到信号集
sigprocmask(SIG_BLOCK, &sigproc, NULL); //添加到阻塞信号集
while(1){
sigpending(&pending);
int i = 1;
for(i = 1;i < 32;i++)
{
if(sigismember(&pending, i) == 1)
printf("1");
else
printf("0");
}
printf("\n");
sleep(1);
}
return 0;
}
运行结果:
由于SIGINT
信号被阻塞所以输入Ctrl+C没有结束程序运行,但是kill -9
仍然能杀死9号信号被阻塞的程序
4.3.10 设置信号处理方式signal()
帮助手册:
man signal
包含头文件:
#include <signal.h>
函数原型:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数说明:设置
signum
信号的处理函数handler
参数 | 说明 |
---|---|
signum | 需要处理的信号 |
handler | 处理信号的函数指针 |
return | 成功:返回先前的信号处理函数的指针 失败:SIG_ERR(-1) |
由于signal
可能有特殊用途,应避免使用,可用sigaction
代替
4.3.11 检查并改变信号动作sigaction()
帮助手册:
man sigaction
包含头文件:
#include <signal.h>
函数原型:
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); // 函数指针(sa_flags=0)
void (*sa_sigaction)(int, siginfo_t *, void *); // 函数指针(sa_flags!=0)
sigset_t sa_mask; // 执行sigaction()函数期间,临时屏蔽的信号集
int sa_flags; // 一般为0,SA_SIGINFO 会使用第二个函数指针
void (*sa_restorer)(void); // 无效
};
参数 | 说明 |
---|---|
signum | 需要处理的信号 |
act | 处理信号的函数指针 |
oldact | 原处理信号的函数指针 |
return | 成功:返回先前的信号处理函数的指针 失败:SIG_ERR(-1) |
示例:
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
void catch_sig(int num)
{
printf("cath %d sig\n", num);
}
int main()
{
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = catch_sig;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
struct itimerval myit = {{3,0},{5,0}};
setitimer(ITIMER_REAL, &myit, NULL);
while(1)
{
printf("...\n");
sleep(1);
}
return 0;
}
运行结果:
利用SIGCHLD
信号回收子进程:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
void catch_sig(int sig)
{
pid_t wpid;
// 避免由于多个子进程同时发出信号而子进程回收不干净
while((wpid = waitpid(-1, NULL, WNOHANG)) > 0)
{
if(wpid > 0)
{
printf("wait child %d successed\n", wpid);
}
}
}
int main()
{
int i = 0;
pid_t pid;
// 在创建子进程之前阻塞SIGCHLD,在注册后解除阻塞,避免注册晚于子进程死亡,而无法回收子进程
sigset_t myset,oldset;
sigemptyset(&myset);
sigaddset(&myset,SIGCHLD);
sigprocmask(SIG_BLOCK,&myset,&oldset);
for(i = 0;i < 10;i++)
{
pid = fork();
if(pid == 0) break;
}
if(i == 10)
{
sleep(5);
struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = catch_sig;
sigaction(SIGCHLD, &act, NULL);
// 解除SIGCHLD信号的阻塞
sigprocmask(SIG_SETMASK,&oldset,NULL);
while(1) sleep(1);
}
else if(i < 10)
{
printf("child process %d,pid=%d\n", i,getpid());
sleep(i);
}
return 0;
}
运行结果: