Linux网络编程【信号】3
信号处理函数:
void(*sa_sigaction)(int signum, siginfo_t *info, void *context);
参数说明:
    signum:信号的编号。
    info:记录信号发送进程信息的结构体。
    context:可以赋给指向 ucontext_t 类型的一个对象的指针,以引用在传递信号时被中断的接收进程或线程的上下文。
示例程序:
//信号处理函数
void fun(int signum)
{
    printf("捕捉到信号%d\n",signum);
}
//新的信号处理函数
void fun1(int signum,siginfo_t *info,void *context)
{
    printf("捕捉到信号:%d\n",signum);
}
//演示sigaction函数的使用
int main()
{
    int ret = -1;
#if 0
    struct sigaction act;
    //使用旧的信号处理函数指针
    act.sa_handler = fun;
    //标志为默认 默认使用旧的信号处理函数指针
    act.sa_flags = 0;
#else
    struct sigaction act;
    //使用新的信号处理函数指针
    act.sa_sigaction = fun1;
    //标志指定使用新的信号处理函数指针
    act.sa_flags = SA_SIGINFO;
#endif
    //信号注册
    ret = sigaction(SIGINT,&act,NULL);
    if(ret == -1)
    {
        perror("sigaction");
        return 1;
    }
    printf("按下任意键退出....\n");
    while(1)
    {
        getchar();
    }
    return 0;
}

9.4 sigqueue 函数(了解)
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
功能:
    给指定进程发送信号。
参数:
    pid : 进程号。
    sig : 信号的编号。
    value : 通过信号传递的参数。
        union sigval 类型如下:
            union sigval
            {
                int   sival_int;
                void *sival_ptr;
            };
返回值:
    成功:0
    失败:-1
向指定进程发送指定信号的同时,携带数据。但如传地址,需注意,不同进程之间虚拟地址空间各自独立,将当前进程地址传递给另一进程没有实际意义。
下面我们做这么一个例子,一个进程在发送信号,一个进程在接收信号的发送。
发送信号示例代码如下:
/*******************************************************
*功能:     发 SIGINT 信号及信号携带的值给指定的进程
*参数:        argv[1]:进程号 argv[2]:待发送的值(默认为100)
*返回值:   0
********************************************************/
int main()
{
    if (argc >= 2)
    {
        pid_t pid, pid_self;
        union sigval tmp;
        pid = atoi(argv[1]); // 进程号
        if (argc >= 3)
        {
            tmp.sival_int = atoi(argv[2]);
        }
        else
        {
            tmp.sival_int = 100;
        }
        // 给进程 pid,发送 SIGINT 信号,并把 tmp 传递过去
        sigqueue(pid, SIGINT, tmp);
        pid_self = getpid(); // 进程号
        printf("pid = %d, pid_self = %d\n", pid, pid_self);
    }
    return 0;
}
接收信号示例代码如下:
// 信号处理回调函数
void signal_handler(int signum, siginfo_t *info, void *ptr)
{
    printf("signum = %d\n", signum); // 信号编号
    printf("info->si_pid = %d\n", info->si_pid); // 对方的进程号
    printf("info->si_sigval = %d\n", info->si_value.sival_int); // 对方传递过来的信息
}
int main()
{
    struct sigaction act, oact;
    act.sa_sigaction = signal_handler; //指定信号处理回调函数
    sigemptyset(&act.sa_mask); // 阻塞集为空
    act.sa_flags = SA_SIGINFO; // 指定调用 signal_handler
    // 注册信号 SIGINT
    sigaction(SIGINT, &act, &oact);
    while (1)
    {
        printf("pid is %d\n", getpid()); // 进程号
        pause(); // 捕获信号,此函数会阻塞
    }
    return 0;
}
两个终端分别编译代码,一个进程接收,一个进程发送,运行结果如下:

10. 不可重入、可重入函数
如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
满足下列条件的函数多数是不可重入(不安全)的:
- 函数体内使用了静态的数据结构;
- 函数体内调用了malloc() 或者 free() 函数(谨慎使用堆);
- 函数体内调用了标准 I/O 函数。
相反,肯定有一个安全的函数,这个安全的函数又叫可重入函数。那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。
保证函数的可重入性的方法:
- 在写函数时候尽量使用局部变量(例如寄存器、栈中的变量);
- 对于要使用的全局变量要加以保护(如采取关中断、信号量等互斥方法),这样构成的函数就一定是一个可重入的函数。
Linux常见的可重入函数:

注意:信号处理函数应该为可重入函数。
11. SIGCHLD信号
11.1 SIGCHLD信号产生的条件
-  子进程终止时 
-  子进程接收到SIGSTOP信号停止时 
-  子进程处在停止态,接受到SIGCONT后唤醒时 
11.2 如何避免僵尸进程
-  最简单的方法,父进程通过 wait() 和 waitpid() 等函数等待子进程结束,但是,这会导致父进程挂起。 
-  如果父进程要处理的事情很多,不能够挂起,通过 signal() 函数人为处理信号 SIGCHLD , 只要有子进程退出自动调用指定好的回调函数,因为子进程结束后, 父进程会收到该信号 SIGCHLD ,可以在其回调函数里调用 wait() 或 waitpid() 回收。 
示例程序:
void sig_child(int signo)
{
    pid_t  pid;
    //处理僵尸进程, -1 代表等待任意一个子进程, WNOHANG代表不阻塞
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0)
    {
        printf("child %d terminated.\n", pid);
    }
}
int main()
{
    pid_t pid;
    // 创建捕捉子进程退出信号
    // 只要子进程退出,触发SIGCHLD,自动调用sig_child()
    signal(SIGCHLD, sig_child);
    pid = fork();   // 创建进程
    if (pid < 0)
    { // 出错
        perror("fork error:");
        exit(1);
    }
    else if (pid == 0)
    { // 子进程
        printf("I am child process,pid id %d.I am exiting.\n", getpid());
        exit(0);
    }
    else if (pid > 0)
    { // 父进程
        sleep(2);   // 保证子进程先运行
        printf("I am father, i am exited\n\n");
        system("ps -ef | grep defunct"); // 查看有没有僵尸进程
    }
    return 0;
}
3)如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
示例程序:
int main()
{
    pid_t pid;
    // 忽略子进程退出信号的信号
    // 那么子进程结束后,内核会回收, 并不再给父进程发送信号
    signal(SIGCHLD, SIG_IGN);
    pid = fork();   // 创建进程
    if (pid < 0)
    { // 出错
        perror("fork error:");
        exit(1);
    }
    else if (pid == 0)
    { // 子进程
        printf("I am child process,pid id %d.I am exiting.\n", getpid());
        exit(0);
    }
    else if (pid > 0)
    { // 父进程
        sleep(2);   // 保证子进程先运行
        printf("I am father, i am exited\n\n");
        system("ps -ef | grep defunct"); // 查看有没有僵尸进程
    }
    return 0;
}
12. 作业
1. 实现类似ps aux | grep "bash"功能
提示: 使用多进程 和execl函数 dup2函数

13.答案
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
//ps aux | grep "bash"
int main(void)
{
    int ret = -1;
    int fd[2] = {0};
    pid_t pid = -1;
    //创建无名管道
    ret = pipe(fd);
    if (-1 == ret)
    {
        perror("pipe"); 
        goto err0;
    }
    //创建一个子进程
    pid = fork();
    if (-1 == pid)
    {
        perror("fork"); 
        goto err0;
    }
    //子进程  ps aux
    if (0 == pid)
    {
        //关闭读端 
        close(fd[0]); 
        dup2(fd[1], STDOUT_FILENO);
        execlp("ps", "ps", "aux", NULL);
        exit(0); 
    }
    else
    {
        //父进程  grep bash
        //关闭写端
        close(fd[1]);
        dup2(fd[0], STDIN_FILENO);
        //先等待子进程结束
        //wait(NULL);
        execl("/bin/grep", "grep", "bash", "--color=auto", NULL);
    }
    return 0;
err0:
    return 1;
}
- 点赞
- 收藏
- 关注作者
 
             
           
评论(0)