Linux程序设计-4.进程通信

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_SHAREDMAP_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);
参数说明
pidpid>0:进程id
pid=0:被调用进程组的所有进程
pid=-1:有权限的进程
pid<-1:给-pid组的进程
sigsig=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;        /* 微秒 */
};
参数说明
whichITIMER_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;
}

运行结果:
示例1运行结果
示例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;
}

运行结果:
示例2运行结果

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决定

参数说明
howSIG_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;
}

运行结果:
利用SIGCHLD信号回收子进程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT灰猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值