UNIX/Linux --信号捕获与处理

二、信号的捕获和处理

 #include <signal.h>

    typedef void (*sighandler_t)(int);

    sighandler_t  signal(int  signum, sighandler_t handler);

    功能:向内核注册一个信号处理函数
    signum:信号的编号,可以直接写数字,也可以使用系统提供的宏。
    handler:函数指针
        SIG_IGN 忽略信号
        SIG_DFL 恢复信号默认的处理方式
    返回值:是之前信号处理方式
                  函数指针、SIG_IGN、SIG_DFL、SIG_ERR

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

void sigsegv(int num)
{
	printf("我捕获了一个信号%d\n",num);
	exit(0);
}

void sigfpe(int num)
{
	printf("我捕获了一个信号%d\n",num);
	exit(0);
}

int main()
{
	signal(SIGSEGV,sigsegv);
	signal(SIGFPE,sigfpe);
	
	int* p = 0x01020304;
	//*p = 100;


	int num = 0;
	int num1 = 100;
	int i = num1/num;
	for(;;);

	return 0;
}


    
    练习:实现一个杀不死的程序。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
	signal(SIGINT,SIG_IGN);
	signal(SIGQUIT,SIG_IGN);
	signal(SIGTSTP,SIG_IGN);

	while(1)
	{
		printf("----\n");
		sleep(1);
	}
	return 0;
}


    1、在有些系统中向内核注册的信号处理函数只执行一次(在执行前就被恢复成默认的处理方式),如果想持续处理信号,可以在每次的处理函数结束时再次注册。
    
    2、SIGKILL、SIGSTOP不能被捕获、也不能被忽略。
    
    3、普通用户只能给自己的进程发信号,超级用户可以能任意进程发送信号。


        三、子进程的信号处理 
    1、通过fork创建的子进程会继承父进程的信号处理方式。
    2、通过vfork+exec创建的子进程不会继承父进程的信号处理方式,会恢复成默认的。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigsegv(int signum)
{
	printf("我捕获到一个段错误,信号编号是%d\n",signum);
}

int main()
{
	if(SIG_ERR==signal(SIGSEGV,sigsegv))
	{
		perror("signal");
		return -1;
	}

	if(0 == fork())
	{
		printf("我是进程%d ,我的父进程是%d\n",getpid(),getppid());
		for(;;);
	}

	printf("我是进程%d ,我的父进程是%d\n",getpid(),getppid());
	for(;;);
}


    
四、信号的发送
    1、键盘
        Ctrl+c 终端中断信号
        Ctrl+z 终端暂停信号,fg命令再次开启
        Ctrl+/ 终端退出信号
    2、错误产生的信号
        除0
        非法内存访问
        硬件总线
    3、命令产生的信号
        kill -信号 进程号
        killall -信号 程序名(杀死所有同名的进程)。
    4、函数产生的信号

 int kill(pid_t pid, int sig);


        功能:向指定的进程发送信号
        pid:与waitpid一样
        sig:信号
            0表示空信号,不会向进程发送信号,但是会测试是否能向pid发送信号,这样可以检测一个进程是否存在,返回-1表示进程不存在,errno为ESRCH。
        返回值:-1,说明进程不存在
        
      

 int raise(int sig);


        功能:向自己发送信号
    
    练习:实现kill命令的功能。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char *argv[])
{
	if(argc <= 2)
	{
		printf("参数太少了\n");
		return -1;
	}

	int pid = atoi(argv[2]);
	int sig = atoi(argv[1]);

	kill(pid,sig);
	printf("%d进程已经死亡\n",pid);
	return 0;

}


    
五、pause


    

#include <unistd.h>
    int pause(void);


    功能:休眠
    
    1、进程调用了pause函数后会进程睡眠状态,直到有信号把它叫醒(不被忽略的信号)。
    2、当信号来临后,先执行信号处理函数,信号处理函数结束后pause再返回。
    3、pause函数要么不返回(一直睡眠),要么返回-1,并且修改errno的值。
    4、从功能上来讲它相当于没有时间限制的sleep函数。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void sigsegv(int signum)
{
	printf("我收到了信号%d\n",signum);
	getchar();
}

int main()
{
	signal(SIGSEGV,sigsegv);
	printf("我是进程%d,我要睡觉了\n",getpid());
	pause();
	perror("pause");
	printf("我睡醒了,呵呵呵呵...\n");
}


    
六、sleep
  

 #include <unistd.h>
    unsigned int sleep(unsigned int seconds);


    功能:使用调用的进程睡眠seconds秒
    
    1、调用sleep的进程如果没有睡眠足够的秒数,除非收到信号后才会返回。
    2、sleep的返回值是0,或剩余的睡眠秒数。
    3、相当于有时间限制的pause
    
    

int usleep(useconds_t usec);v


    功能:睡眠usec微秒
    
    1秒=1000毫秒=1000000微秒。
    它是一种更精确的睡眠函数。
    
    -std=c99 不建议使用
    -std=gnu99 在使用系统调用时一定要使用此标准
    
七、alarm
    

#include <unistd.h>
    unsigned int alarm(unsigned int seconds);


    功能:定时一个闹钟信号
    
    1、让内核向调用它的进程,在seconds秒后发送一个SIGALRM信号。
    2、SIGALRM信号的默认处理方式是直接退出。
    
    练习:实现一个闹钟命令,./alarm sec
    
八、信号集和信号屏蔽
    1、信号集:
        多个信号的集合,sigset_t
        由128个二进制位组成,每个二进制位表示一个信号
        
        int sigemptyset(sigset_t *set);
        功能:清空信号集
        
        int sigfillset(sigset_t *set);
        功能:填满信号信
        
        int sigaddset(sigset_t *set, int signum);
        功能:向信号集中添加信号
        
        int sigdelset(sigset_t *set, int signum);
        功能:从信号集中删除信号
        
        int  sigismember(const sigset_t *set, int
       signum);
           功能:测试一个信号集中是否有某个信号
           返回值:有返回1,没有返回0,失败返回-1
    2、屏蔽信号集中的信号
        每个进程都有一个信号掩码(signal mask),它就是一个信号集,里面包含了进程所屏蔽的信号。
        
        int sigprocmask(int how, const  sigset_t
       *set, sigset_t *oldset);
       功能:设置进程的信号掩码(信号屏蔽码)
       how:修改信号掩码的方式
               SIG_BLOCK:向信号掩码中添加信号
               SIG_UNBLOCK:从信号掩码中删除信号
               SIG_SETMASK:用新的信号集替换旧的信号掩码
       newset:新添加、删除、替换的信号集,也可以为空
       oldset:获取旧的信号掩码
       当newset为空时,就是在备份信号掩码

    当进程执行一些敏感操作时不希望被打扰(原子操作),此需要向屏蔽信号。
    屏蔽信号的目的不是为了不接收信号,而是延时接收,当处理完要做的事情后,应该把屏蔽的信号还原。
    当信号屏蔽时发生的信号会记录一次,这个信号设置为末决状态,当信号屏蔽结束后,会再发送一次。
    不可靠信号在信号屏蔽期间无论信号发生多少次,信号解除屏蔽后,只发送一次。
    可靠信号在信号屏蔽期间发生的信号会排队记录,在信号解除屏蔽后逐个处理。
    在执行处理函数时,会默认把当前处理的信号屏蔽掉,执行完成后再恢复
    
    int sigpending(sigset_t *set);
    功能:获取末决状态的信号
    
九、信号处理sigaction signal
    

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);


    功能:设置或获取信号处理方式
    
     struct sigaction {
         // 信号处理函数指针
         void (*sa_handler)(int);
         // 信号处理函数指针 需要使用sigqueue发送信号
        void (*sa_sigaction)(int, siginfo_t *, void *);
        // 信号屏蔽码
        sigset_t sa_mask;
        int sa_flags;
            SA_NOCLDSTOP:忽略SIGCHLD信号
            SA_NODEFER/SA_NOMASK:在处理信号时不屏蔽信号
            SA_RESETHAND:处理完信号后,恢复系统默认处理方式
            SA_RESTART:当信号处理函数中断的系统调用,则重启系统调用。
            SA_SIGINFO:用sa_sigaction处理信号
        // 保留
        void (*sa_restorer)(void);
     };
     
     
     

int sigqueue(pid_t pid, int  sig,  const union sigval value);


 

猜你喜欢

转载自blog.csdn.net/weixin_42205987/article/details/81208688
今日推荐