POSIX信号量

发布时间 2023-10-12 11:16:06作者: eiSouthBoy

背景介绍

多进程之间的同步机制:信号量。而在多线程编程中,通常更常见的是使用线程之间的同步机制,例如互斥锁、条件变量、信号量等,来实现线程之间的协调和通信。这些机制更适合用于线程级别的同步和通信需求。

POSIX信号

信号(signal)就是告知某一进程发生了某个事件的通知,有时也称为软件中断(software interrupt)。
信号通常是 异步 发生的,也就是说:进程预先不知道信号的准确发生时刻。

信号发送方式:

1)由一个进程发送给另一个进程(或自身)

2)由内核发送给某个进程

一般来说某一个进程需要定义一个信号回调函数用来等待一个特定信号的到来,只要有特定信号发生,信号处理函数就会被调用。一个signal()只能针对某一信号的处理,若要处理多个信号就要注册多个signal()
每一个信号都有一个名字,这些名字都是以SIG开头,且信号名被定义为正整数常量。不同的操作系统所能支持的信号数量都有不同。

在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或与信号相关的动作。

1)忽略此信号。大多数信号都可以使用这种方式进行处理,但 SIGKILLSIGSTOP 不能被忽略。

2)捕捉信号。SIGKILLSIGSTOP 不能被捕捉

3)执行系统默认动作。对于大多数信号的系统默认动作时终止进程。

信号表:

名字 说明 Linux 3.2.0 默认动作
SIGABRT 异常终止(abort) yes 终止+core
SIGALRM 定时器超时(alarm) yes 终止
SIGBUS 硬件故障 yes 终止+core
SIGCHLD 子进程状态改变 yes 忽略
SIGCONT 使暂停进程继续 yes 继续/忽略
SIGEMT 硬件故障 yes 终止+core
SIGFPE 算术异常 yes 终止+core
SIGHUP 连接断开 yes 终止
SIGILL 非法硬件指令 yes 终止+core
SIGINT 终端中断符 yes 终止
SIGIO 异步I/O yes 终止/忽略
SIGIOT 硬件故障 yes 终止+core
SIGKILL 终止 yes 终止
SIGPIPE 写至无读进程的管道 yes 终止
SIGPOLL 可轮询事件(poll) yes 终止
SIGPROF 梗概事件超时(setitimer) yes 终止
SIGPWR 电源失效/重启动 yes 终止+core
SIGQUIT 终端退出符 yes 终止+core
SIGSEGV 无效内存引用 yes 终止+core
SIGSTKFLT 协处理器栈故障 yes 终止
SIGSTOP 停止 yes 停止进程
SIGSYS 无效系统调用 yes 终止+core
SIGTERM 终止 yes 终止
SIGTRAP 硬件故障 yes 终止+core
SIGTSTP 终端停止符 yes 停止进程
SIGTTIN 后台读控制 tty yes 停止进程
SIGTTOU 后台写向控制 tty yes 停止进程
SIGURG 紧急情况(套接字) yes 忽略
SIGUSR1 用户定义信号 yes 终止
SIGUSR2 用户定义信号 yes 终止
SIGVTALRM 虚拟事件闹钟(setitimer) yes 终止
SIGWINCH 终端窗口大小改变 yes 忽略
SIGXPU 超过CPU限制(setrlimit) yes 终止或终止+core
SIGXFSZ 超过文件长度限制(setrlimit) yes 终止或终止+core

signal()

/* 包含的头文件 */
#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler); // 若出错,返回SIG_ERR

kill进程发送信号

测试SIGUSR1、SIGUSR2、SIGINT信号的捕获:
sig_usr.c

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <signal.h>

static void sig_usr(int signo)
{
	if (signo == SIGUSR1)
		printf("received SIGUSR1\n");
	else if (signo == SIGUSR2)
		printf("received SIGUSR2\n");
	else if (signo == SIGINT)
		printf("received SIGINT\n");
	else
		printf("received signal %d\n", signo);
}

int main(void)
{
	if (signal(SIGUSR1, sig_usr) == SIG_ERR)
	{
		printf("can't catch SIGUSR1\n");
	}
	if (signal(SIGUSR2, sig_usr) == SIG_ERR)
	{
		printf("can't catch SIGUSR2\n");
	}
	if (signal(SIGINT, sig_usr) == SIG_ERR)
	{
		printf("can't catch SIGINT\n");
	}
	while (true)
	{
		pause();
	}

	exit(0);
}

子进程结束发送信号

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <pthread.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>

static int g_running = 0;

static int handle(void)
{
    int second = 5;
    printf("sleep 5s");
    while (second--)
    {
        printf(".");
        fflush(stdout);
        sleep(1);
    }
    printf("\n");
    return 0;
}

static void sig_handler(int signo)
{
	pid_t pid;
    while (1)
    {
        pid = waitpid(-1, NULL, WNOHANG);
        if (pid > 0)
        {
            printf("child %d terminated\n", pid);
            g_running = 0;
        }
        if (pid == -1 || pid == 0)
        {
            break;
        }
    }
}

int main(void)
{
    g_running = 1;
    if (signal(SIGCHLD, sig_handler) == SIG_ERR)
	{
		printf("can't catch SIGCHLD\n");
        exit(1);
	}

    int pid = 0;
    pid = fork();
    if (pid < 0)
    {
        printf("fork error\n");
        exit(1);
    }
    else if (pid == 0)
    {
        handle();
        exit(1);
    }
    while (g_running)
    {
        pause();
    }
    pid_t pidMain;
    pidMain = getpid();
    printf("main %d exit\n", pidMain);
    exit(0);
}