栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

多线程和多进程标准信号处理API

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

多线程和多进程标准信号处理API

标准信号处理

文章目录
  • 标准信号处理
  • 忽略信号
  • 向PID号为1234的进程发送2号信号:
  • 信号的捕捉
  • 将 core 文件的大小设置为“不限制”
  • 信号API作业1
  • 信号API作业2

忽略信号
 忽略信号SIGINT    signal(SIGINT, SIG_IGN)
#include 
#include 

//pid是接收信号的进程PID,称为目标进程。
//sig是信号的编号。
int kill(pid_t pid, int sig);
下面的代码实现向指定PID的进程发送2号信号SIGINT(该信号就是平时按ctrl+c产生的键盘中断信号:

#include 
#include 
#include 
int main(int argc, char **argv)
{
    if(argc == 2)
    	kill(atoi(argv[1]), SIGSEGV);
    return 0;
}
向PID号为1234的进程发送2号信号:

gec@ubuntu:~$ kill -2 1234
信号的屏蔽

#include 
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数简析:
how:操作命令字,比如阻塞、解除阻塞等
set:当前要操作的信号集
oldset:若为非空,则将原有阻塞信号集保留到该oldset中

信号集(sigset_t),这意味着我们可以同时对多个信号设置阻塞或解除阻塞。

// 信号集操作函数组
#include 
int sigemptyset(sigset_t *set);   // 清空信号集set
int sigfillset(sigset_t *set);    // 将所有信号加入信号集set中
int sigaddset(sigset_t *set, int signum); // 将信号signum添加到信号集set中
int sigdelset(sigset_t *set, int signum); // 将信号signum从信号集set中剔除
int sigismember(const sigset_t *set, int signum); // 测试信号signum是否在信号集set中
SIG_BLOCK:阻塞set中的信号(原有正在阻塞的信号保持阻塞)。
SIG_SETMASK:阻塞set中的信号(原有正在阻塞的信号自动解除)。
SIG_UNBLOCK:解除set中的信号。
// 将1、2号信号加入信号集
sigset_t sig;
sigemptyset(&sig);
sigaddset(&sig, SIGHUP); // 加入1号信号
sigaddset(&sig, SIGINT); // 加入2号信号
// 阻塞1、2号信号
setprocmask(SIG_SETMASK, &sig, NULL);
信号的捕捉
#include 
void (*signal(int sig, void (*func)(int)))(int);
返回值类型:void (*)(int);
返回值含义:返回一个指向原有的与指定信号关联的函数
参数:
sig: 指定要关联的信号
func:指定要关联的响应函数

// 标准信号响应函数接口
void func(int sig)
{
// …
}
Term:中断目标进程。
Core:中断目标进程,且产生核心转储文件core。
Stop:暂停目标进程,直到收到信号SIGCONT
Cont:恢复目标进程运行
Ign:忽略信号
//查看当前系统对 core 文件的限制
gec@ubuntu:~$ ulimit -a

将 core 文件的大小设置为“不限制”

gec@ubuntu:~$ ulimit -c unlimited
忽略信号
忽略信号就是直接将收到的信号丢弃,做法如下:
int main()
{
// 忽略信号SIGINT
signal(SIGINT, SIG_IGN);
}

信号API作业1

【1】编写两个程序,一个代表司机,一个代表售票员。
他们之间的交互如下:
售票员向司机发某信号,司机输出“开车”。
售票员向司机发某信号,司机输出“靠站”。
司机向售票员发某信号,售票员输出“到总站,全部下车”之后退出,同时司机也退出。
信号巴士

解答:
本题的难点在于司机和售票员双方需要互相知道对方的PID方可发送信号,由于它们不属于亲缘进程,因此可以使用具名管道互相通知彼此的进程PID,并且由于管道在默认情况下是阻塞的,那么在对方尚未启动的情况下恰好可以利用管道的阻塞特性来等待对方的启动。
示例代码如下:
// 司机:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

pid_t saler;

void operate(int sig)
{
	switch(sig)
	{
	case SIGUSR1:
		fprintf(stderr, "司机:好的,坐好扶稳出发!nn");
		break;
	case SIGUSR2:
		fprintf(stderr, "司机:好的, 停稳靠站了!nn");
		break;
	}
}

int main(void)
{
	mkfifo("/tmp/driverPid", 0666);
	mkfifo("/tmp/salerPid", 0666);

	int fd1 = open("/tmp/driverPid", O_CREAT|O_RDWR, 0666);
	int fd2 = open("/tmp/salerPid", O_RDWR);

	// 将自身PID写入管道
	pid_t myPid = getpid();
	write(fd1, &myPid, sizeof(myPid));

	printf("正在尝试读取售票员PID... ...n");
	read(fd2, &saler, sizeof(saler));
	printf("读取售票员PID成功n");

	signal(SIGUSR1, operate);
	signal(SIGUSR2, operate);

	int cmd;
	printf("按下1代表到总站,线路停运。n");
	while(1)
	{
		scanf("%d", &cmd);

		if(cmd == 1)
		{
			kill(saler, SIGQUIT);
			printf("欢迎下次再次乘坐本趟公交!再见!n");
			break;
		}
	}

	// 删除相关管道文件
	unlink("/tmp/driverPid");
	unlink("/tmp/salerPid");

	return 0;
}

// 售票员:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

pid_t driver;

void quit(int sig)
{
	printf("售票员:到总站了,都下车吧!n");

	unlink("/tmp/driverPid");
	unlink("/tmp/salerPid");

	exit(0);
}

int main(void)
{
	mkfifo("/tmp/driverPid", 0666);
	mkfifo("/tmp/salerPid", 0666);

	int fd1 = open("/tmp/driverPid", O_CREAT|O_RDWR, 0666);
	int fd2 = open("/tmp/salerPid", O_RDWR);

	// 将自身PID写入管道
	pid_t myPid = getpid();
	write(fd2, &myPid, sizeof(myPid));

	printf("正在尝试读取司机PID... ...n");
	read(fd1, &driver, sizeof(driver));
	printf("读取司机PID成功n");

	signal(SIGQUIT, quit);

	int cmd;
	printf("按下1代表靠站停车,按2代表开始出发。n");
	while(1)
	{
		scanf("%d", &cmd);

		if(cmd == 1)
			kill(driver, SIGUSR1);
		else if(cmd == 2)
			kill(driver, SIGUSR2);
	}

	return 0;
}
信号API作业2

【2】用信号集相关函数,信号发送和注册函数等相关知识。
编写代码验证以下结论:
进程的信号挂起队列中,没有相同的信号(即相同的信号会被丢弃)。
进程在响应信号时,信号会相互嵌套。
挂起信号不会被子进程继承,但信号阻塞掩码会被继承。
解答:
分别解释上述三个结论:
当一个信号发给目标进程后,在进程处理该信号前会存储在系统的信号挂起队列中,若此时继续向进程发送相同的信号,由于信号是以信号集方式存储的,一个信号集中无法储存多个相同的信号,因此这些后续到达的信号将不起作用。验证代码如下:

#include 
#include 
#include 
#include 
#include 

void sighand(int sig)
{
	if(sig == SIGUSR1)
		printf("receive a SIGUSR1!n");

	return;
}

int main(void)
{
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGUSR1);

	signal(SIGUSR1, sighand);
	
	// block SIGUSR1
	sigprocmask(SIG_BLOCK, &sig, NULL);

	printf("my pid: %d, send me two SIGUSR1 in 20s.n", getpid());
	int i = 20;
	while(i--)
	{
		printf("%dn", i);
		sleep(1);
	}

    // 在解除信号阻塞掩码前的20秒内,持续向本进程发送SIGUSR1
    // 20秒后解除阻塞会发现信号只被触发一次,由此可证明本期结论
	sigprocmask(SIG_UNBLOCK, &sig, NULL);

	while(1)
		pause();

	return 0;
}

不同信号的响应函数可以相互嵌套,也就会说当响应一个信号尚未结束而又来了另一个信号时,进程会被迫去执行另一个信号的响应函数,以下代码可供验证此结论:

#include 
#include 
#include 
#include 
#include 

void sighand(int sig)
{
    // 收到信号SIGUSR1时,缓慢输出SIGUSR1 received
	if(sig == SIGUSR1)
	{
		char str[] = "SIGUSR1 receiced";
		int i = 0;
		while(str[i] != '')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}

    // 收到信号SIGUSR2时,缓慢输出catch a SIGUSR2
	if(sig == SIGUSR2)
	{
		char str[] = "catch a SIGUSR2";
		int i = 0;
		while(str[i] != '')
		{
			fprintf(stderr, "%c", str[i++]);
			sleep(1);
		}
	}
	return;
}

int main(void)
{
	signal(SIGUSR1, sighand);
	signal(SIGUSR2, sighand);

	printf("my pid: %d, send me SIGUSR1 and SIGUSR2.n", getpid());

    // 连续给本进程发送SIGUSR1和SIGUSR2,可验证本题结论
	while(1)
		pause();

	return 0;
}

第三个结论可以分两个小结论来看,一是挂起信号不会被子进程继承,二是信号阻塞掩码会被继承。那么只需在创建子进程之前,设置好阻塞掩码,并提前接收一些信号使之挂起不处理,然后在子进程中观察这些信号的行为即可。下述代码可验证本题结论:

#include 
#include 
#include 
#include 

void catch_sig(int sig)
{
	if(sig == SIGINT)
		fprintf(stderr, "%d catch SIGINT.n", getpid());

	else if(sig == SIGQUIT)
		fprintf(stderr, "%d catch SIGQUIT.n", getpid());
}

int main(int argc, char **argv)
{
    // 捕捉信号SIGQUIT
	signal(SIGQUIT, catch_sig);

    // 阻塞信号SIGQUIT
	sigset_t sig;
	sigemptyset(&sig);
	sigaddset(&sig, SIGQUIT);
	sigprocmask(SIG_BLOCK, &sig, NULL);

    // 在接下来的15秒内给本进程发送SIGQUIT,这些信号将被挂起
	sleep(15);

	pid_t pid;
	pid = fork();

	if(pid == 0)
    {
		fprintf(stderr, "child: %dn", getpid());

        // 在子进程中解除对SIGQUIT的阻塞
        // 若子进程没有响应该信号,则可验证父进程挂起的信号不会继承给子进程

		// 另外,注释掉以下语句后,单独给子进程发送SIGQUIT会发现子进程不会响应该信号
		// 则可验证父进程设置的信号掩码会被子进程继承
		sigprocmask(SIG_UNBLOCK, &sig, NULL);

		while(1)
			pause();
	}
	
	else if(pid > 0)
    {
		fprintf(stderr, "parent: %dn", getpid());
		sigprocmask(SIG_UNBLOCK, &sig, NULL);
		while(1)
			pause();
	}

	return 0;
}

编程实现两个程序,模拟智能公交的某两个功能模块程序:
信号发送者A,每次发信号SIGUSR1通知B时顺带发去当前乘客数
信号接收者B,每次收到SIGUSR1时读取当前乘客数,并暂停响应其他信号。

解答:
这是一个利用扩展信号接口的典型应用,注意要使用sigqueue和sigaction配合。代码如下:
// 信号发送者A

#include 
#include 
#include 
#include 

int main(int argc, char **argv)
{
    union sigval val;
    val.sival_int = 100;

    // 给指定进程B发送信号SIGUSR1
    sigqueue(atoi(argv[1]), SIGUSR1, val);

    return 0;
}

// 信号接受者B
#include 
#include 
#include 

void func(int sig, siginfo_t *info, void *arg)
{
    printf("当前乘客数:%dn", info->si_int);
}

int main(int argc, char **argv)
{
    struct sigaction act;
    bzero(&act, sizeof(act));

    act.sa_sigaction = func;
    act.sa_flags |= SA_SIGINFO;

    // 在响应SIGUSR1期间,临时阻塞其他所有信号
    sigfillset(&act.sa_mask);
    sigaction(SIGUSR1, &act, NULL);

    while(1)
        pause();

    return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/679486.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号