Android C++系列:Linux信号(三)

「这是我参与11月更文挑战的第28天,活动详情查看:2021最后一次更文挑战」。

可重入函数

image-20211128083606673.png

  • 不含全局变量和静态变量是可重入函数的一个要素
  • 可重入函数见man 7 signal
  • 在信号捕捉函数里应使用可重入函数
  • 在信号捕捉函数里禁止调用不可重入函数

例如:strtok就是一个不可重入函数,因为strtok内部维护了一个内部静态指针,保存上一 次切割到的位置,如果信号的捕捉函数中也去调用strtok函数,则会造成切割字符串混乱, 应用strtok_r版本,r表示可重入。

信号引起的竞态和异步I/O

时序竞态

  • int pause(void)

    • 使调用进程挂起,直到有信号递达,如果递达信号是忽略,则继续挂起
  • int sigsuspend(const sigset_t *mask)

    • 以通过指定mask来临时解除对某个信号的屏蔽,
    • 然后挂起等待,
    • 当被信号唤醒sigsuspend返回时,进程的信号屏蔽字恢复为原来的值

mysleep实现,这种实现方式是否存在BUG?

#include <unistd.h> 
#include <signal.h> 
#include <stdio.h>
void sig_alrm(int signo) {
	/* nothing to do */ 
}
unsigned int mysleep(unsigned int nsecs) {
	struct sigaction newact, oldact; 
	unsigned int unslept;
	newact.sa_handler = sig_alrm; 
	sigemptyset(&newact.sa_mask); 
	newact.sa_flags = 0; 
	sigaction(SIGALRM, &newact, &oldact);
	alarm(nsecs); 
	pause();
	unslept = alarm(0); 
	sigaction(SIGALRM, &oldact, NULL);
	return unslept; 
	}
int main(void) {
	while(1){
		mysleep(2);
		printf("Two seconds passed\n"); 
	}
	return 0; 
}
复制代码

mysleep改进版

unsigned int mysleep(unsigned int nsecs) {
	struct sigaction newact, oldact;
	sigset_t newmask, oldmask, suspmask;
  unsigned int unslept;
	/* set our handler,save previous information */
	newact.sa_handler = sig_alrm;
	sigemptyset(&newact.sa_mask); 
	newact.sa_flags = 0; 
	sigaction(SIGALRM, &newact, &oldact);
	/* block SIGALRM and save current signal mask */ 
	sigemptyset(&newmask);
	sigaddset(&newmask, SIGALRM); 
	sigprocmask(SIG_BLOCK, &newmask, &oldmask);
	alarm(nsecs);
	suspmask = oldmask; 
	sigdelset(&suspmask, SIGALRM); /* make sure SIGALRM isn't blocked */ 
	sigsuspend(&suspmask);/* wait for any signal to be caught */
	/* some signal has been caught,SIGALRM is now blocked */
	unslept = alarm(0);
	sigaction(SIGALRM, &oldact, NULL);/* reset previous action */
 
	/* reset signal mask, which unblocks SIGALRM */ 
	sigprocmask(SIG_SETMASK, &oldmask, NULL); 
	return(unslept);
}
复制代码

全局变量异步I/O

可重入函数

  1. 不含全局变量和静态变量是可重入函数的一个要素
  2. 可重入函数见man 7 signal
  3. 在信号捕捉函数里应使用可重入函数

避免异步I/O的类型

  • sig_atomic_t 平台下的原子类型

  • volatile 防止编译器开启优化选项时,优化对内存的读写

SIGCHLD信号处理

SIGCHLD的产生条件

  • 子进程终止时
  • 子进程接收到SIGSTOP信号停止时
  • 子进程处在停止态,接受到SIGCONT后唤醒时

代码实现

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <signal.h>
void sys_err(char *str) {
	perror(str);
	exit(1); 
}
void do_sig_child(int signo) {
	int status;
	pid_t pid;
	while ((pid = waitpid(0, &status, WNOHANG)) > 0) {
		if (WIFEXITED(status))
			printf("child %d exit %d\n", pid, WEXITSTATUS(status));
		else if (WIFSIGNALED(status))
			printf("child %d cancel signal %d\n", pid, WTERMSIG(status));
	}
}
int main(void) {
	pid_t pid;
	int i;
	//阻塞SIGCHLD
	for (i = 0; i < 10; i++) {
		if ((pid = fork()) == 0) 
			break;
		else if (pid < 0) 
			sys_err("fork");
	}
	if (pid == 0) {
		int n = 18; while (n--) {
			printf("child ID %d\n", getpid());
			sleep(1); 
		}
		return i; 
	}else if (pid > 0) { //先设置捕捉
		//再解除对SIGCHLD的阻塞 
		struct sigaction act;
		act.sa_handler = do_sig_child; 
		sigemptyset(&act.sa_mask); act.sa_flags = 0; 
		sigaction(SIGCHLD, &act, NULL); 
		while (1) {
			printf("Parent ID %d\n", getpid());
			sleep(1); 
		}
	}
	return 0; 
}
复制代码

status处理方式

pid_t waitpid(pid_t pid, int *status, int options)

  • options
    • WNOHANG 没有子进程结束,立即返回
    • WUNTRACED
    • 如果子进程由于被停止产生的SIGCHLD, waitpid则立即返回
    • WCONTINUED 如果子进程由于被SIGCONT唤醒而产生的SIGCHLD, waitpid则立即返回
  • 获取status
    • WIFEXITED(status) 子进程正常exit终止,返回真 WEXITSTATUS(status)返回子进程正常退出值
    • WIFSIGNALED(status) 子进程被信号终止,返回真
    • WTERMSIG(status)返回终止子进程的信号值 WIFSTOPPED(status)
    • 子进程被停止,返回真 WSTOPSIG(status)返回停止子进程的信号值
    • WIFCONTINUED(status) 子进程由停止态转为就绪态,返回真

向信号捕捉函数传参

sigqueue

int sigqueue(pid_t pid, int sig, const union sigval value) union sigval {
int sival_int;
void *sival_ptr; };
复制代码

sigaction

void (*sa_sigaction)(int, siginfo_t *, void *) 
siginfo_t {
	int si_int; 
	void *si_ptr; 
	sigval_t si_value;
	...
}
sa_flags = SA_SIGINFO
/* POSIX.1b signal */ /* POSIX.1b signal */ /* Signal value */
复制代码

实例

  • 进程自己收发信号,在同一地址空间
  • 不同进程间收发信号,不在同一地址空间,不适合传地址

信号中断系统调用

read阻塞时,信号中断系统调用:

  1. 返回部分读到的数据
  2. read调用失败,errno设成EINTER

总结

本文介绍了可重入函数,信号引起的竞态和异步I/O,SIGCHLD信号处理,向想好捕捉函数传参,信号中断系统调用。

Guess you like

Origin juejin.im/post/7035416934657032229