moc 发表于 2018-11-9 16:15:44

013-信号进阶

本帖最后由 moc 于 2018-11-15 17:03 编辑

1、信号在linux中的传递过程
信号递达(Delivery): 执行信号的处理动作称为。
信号未决(Pending):信号从产生到递达之间的状态。
        进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
        注意,阻塞和忽略是不同,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
信号在linux中的传递过程:

说明1: PCB进程控制块中函数有信号屏蔽状态字(block)信号未决状态字(pending)还有是否忽略标志;
说明2: 信号屏蔽状态字(block),1代表阻塞、0代表不阻塞;信号未决状态字(pending)的1代表未决,0代表信号可以抵达了;
说明3:向进程发送SIGINT,内核首先判断信号屏蔽状态字是否阻塞,若阻塞则信号未决状态字相应位制成1;若阻塞解除,信号未决状态字相应位制成0;表示信号可以抵达了。
说明4: block状态字、pending状态字 都是64bit;
说明5: block状态字用户可以读写,pending状态字用户只能读, 这是信号设计机制。
信号集操作函数(状态字表示):
头文件:#include <signal.h>

原型|功能
intsigemptyset(sigset_t *set)| 把信号集64bit全设为0
intsigfillset(sigset_t *set)|把信号集全设为1
intsigaddset(sigset_t *set, int signo)|根据signo,把信号集中的对应为置成1
intsigdelset(sigset_t *set, int signo)|根据signo,把信号集中的对应为置成0
intsigismember(const sigset_t *set, int signo)|判断signo是否在信号集中
int sigprocmask(int how, const sigset_t *set, sigset_t *oset)|读取或更改进程的信号屏蔽状态字(block)
int sigpending(sigset_t *set)|获取信号未决状态字(pending)信息

sigprocmask函数:
功能: 读取或更改进程的信号屏蔽字。
返回值: 若成功则为0,若出错则为-1
        如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
        如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
        如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

// 演示信号从产生到抵达的整个过程
// 信号的阻塞和解除阻塞的综合实验
#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0);
       
// 信号处理函数
void handlder(int sig)
{
        if (sig == SIGINT)
        {
                printf("recv a sig = %d \n", sig);
        }
        else if (sig == SIGQUIT)
        {
                sigset_t uset;   // 自建一个信号集
                sigemptyset(&uset);   // 清空信号集
                sigaddset(&uset, SIGINT);
                // Ctrl + \ 来解除 SIGINT 信号
                // 解除阻塞
                sigprocmask(SIG_UNBLOCK, &uset, NULL);// 更改信号屏蔽字block
        }
}
// 打印信号集
void printsigset(sigset_t *set)
{
        int i;
        for (i=1; i<NSIG; ++i)
        {
                if (sigismember(set, i))// 查看信号集中是否有i信号
                {
                        putchar('1');
                }
                else
                {
                        putchar('0');
                }
        }
        printf("\n");
}
// 连续按键盘 Ctrl + c ,虽然发送了多个SIGINT信号,但因为信号是不稳定的,只保留了一个
// 不支持排队   PCB==> task_struct
int main(int argc, char *argv[])
{
        sigset_t pset;// 用来打印信号集   未决状态字
        sigset_t bset;// 用来设置阻塞信号集
       
        sigemptyset(&bset);
        sigaddset(&bset, SIGINT);   // 把信号集中相应的位置1
       
        // 注册信号
        if (signal(SIGINT, handlder) == SIG_ERR)
        {
                ERR_EXIT("signal error");
        }
        if (signal(SIGQUIT, handlder) == SIG_ERR)
        {
                ERR_EXIT("signal error");
        }
       
        // 读取或更改进程信号的屏蔽字这里来阻塞信号Ctrl + c信号
        // Ctrl + c信号被设置从阻塞, 即使用户按下ctrl+c键,也不会抵达
        sigprocmask(SIG_BLOCK, &bset, NULL);// 或的方式设置阻塞位
       
        for (;;)
        {
                // 获取未决 状态字信息
                sigpending(&pset);
               
                // 打印信号未决sigset_t 字
                printsigset(&pset);
                sleep(2);
        }
        return 0;
}
2、sigaction函数注册信号处理函数、sigqueue新的信号发送函数
可带值的信号传递。
1. sigaction函数
功能: sigaction函数用于改变进程接收到特定信号后的行为。
原型: intsigaction(int signum,const struct sigaction *act,const struct sigaction *old);
参数:         1>第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误);
                2> 第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;
                3> 第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
返回值:   函数成功返回0,失败返回-1.
2. sigaction结构体
struct sigaction {
    void (*sa_handler)(int);        // 此参数和signal()的参数handler相同,代表新的信号处理函数,不接受额外数据
    void (*sa_sigaction)(int, siginfo_t *, void *);        // 信号处理程序 能接受额外数据,和sigqueue配合使用
    sigset_t sa_mask;        // 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置,信号最终还会抵达
    int sa_flags;        // //影响信号的行为,可选SA_SIGINFO表示能接受数据
}
注意:回调函数句柄sa_handler、sa_sigaction只能任选其一。
3. siginfo_t结构体
siginfo_t {
                  int      si_signo;/* Signal number */
                  int      si_errno;/* An errno value */
                  int      si_code;   /* Signal code */
                  pid_t    si_pid;    /* Sending process ID */
                  uid_t    si_uid;    /* Real user ID of sending process */
                  int      si_status; /* Exit value or signal */
                  clock_tsi_utime;/* User time consumed */
                  clock_tsi_stime;/* System time consumed */
                  sigval_t si_value;/* Signal value */
                  int      si_int;    /* POSIX.1b signal */
                  void *   si_ptr;    /* POSIX.1b signal */
                  void *   si_addr;   /* Memory location which caused fault */
                  int      si_band;   /* Band event */
                  int      si_fd;   /* File descriptor */
            }sigqueue函数
功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
原型:int sigqueue(pid_t pid, int sig, const union sigval value);
参数:        第1个参数是指定接收信号的进程id,
                第2个参数确定即将发送的信号,
                第3个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
返回值:           成功返回0,失败返回-1
注意:        和kill函数相比Int kill(pid_t pid, int siq)多了参数;sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
sigval联合体
typedef union sigval {
                int sival_int;
                void *sival_ptr;
}sigval_t;
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

// 演示发送和接受带数据的信号
#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0);
       
typedef struct Teacher
{
        char name;
        int age;
}Teacher;

// 用新的回调函数接受数据....
void myHandle_forsigaction(int signum, siginfo_t *s_t, void *p)
{
        int myint = 0;
        printf("可传输数据的回调函数.\n");
        printf("recv signum : %d \n", signum);
        myint = s_t->si_value.sival_int;
        printf("%d %d \n", myint, s_t->si_int);
}

int main(int argc, char *argv[])
{
        pid_t pid;
        int ret = 0;
       
        struct sigaction act;
        act.sa_sigaction = myHandle_forsigaction;
        sigemptyset(&act.sa_mask);// sa_mask集中的信号在处理sa_sigaction函数时会暂时阻塞
       
        // 父进程的回调函数, 准备接受额外的数据
        act.sa_flags = SA_SIGINFO;
       
        if (sigaction(SIGINT, &act, NULL) < 0)
        {
                ERR_EXIT("sigaction error");
        }
       
        pid = fork();
        if (pid == -1)
        {
                perror("fork error");
                return 0;
        }
        if (pid == 0)
        {
                union sigval mysigval;
                mysigval.sival_int = 222;
               
                ret = sigqueue(getppid(), SIGINT, mysigval);
                if (ret != 0)
                {
                        printf("sigqueue .....\n");
                        exit(0);
                }
                else
                {
                        printf("sigqueue ...sucecess\n");
                        sleep(2);
                }
        }
        else if (pid > 0)
        {
                for (;;)
                {
                        pause();
                }
        }
        return 0;
}
页: [1]
查看完整版本: 013-信号进阶