zoukankan      html  css  js  c++  java
  • 实验七 信号

    [实验七 信号]

    项目 内容
    这个作业属于哪个课程 课程链接
    这个作业的要求在哪里 作业要求链接
    学号-姓名 17041514-乐驰
    作业学习目标 1、了解信号的概念 2 、掌握信号处理的方法
    1、 编写一个简单的程序并运行,然后向该进程发送不同的信号以观察该进程对接收到信号的反应。

    //hellosignal.c
    #include <stdio.h> 
    #include <unistd.h> 
    int main() 
    { 
    	printf("hello signal! I'm %d
    ", getpid()); 
    	while(1) 
    	{ 
    		write(STDOUT_FILENO, ".", 1); 
    		sleep(10); 
    	}
    return 0;
    }
    在终端编译并运行该程序
    

    //hellosignal.c(修改后)
    #include <stdio.h> 
    #include <unistd.h> 
    int main() 
    { 
    	printf("Hello Signal! I'm %d
    ", getpid()); 
    	while(1) 
    	{ 
    		write(STDOUT_FILENO, "*", 1); 
    		sleep(10); 
    	}
    return 0;
    }
    

    (2)另外再开启一个终端,在终端通过输入 kill 命令来给进程发送信号,进程的 pid 在程序运行的第一行输出。
    
    在终端通过输入 kill -l 来查看当前系统当中的信号列表:
    

    重新运行程序:
    

    看到程序输出自己的 pid 是2928,在另开一个终端,通过 kill 命令向该进程发送信号;
    
    再切换到运行程序的终端来观察进程接收到信号后的反应:
    

    对于 kill 命令我们可以查看手册:
    
     man kill
    

    (3)编写一个简单的程序,该程序调用 kill() 函数向某个进程发送信号
    //mykill.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <signal.h>
    int main(int argc,char *argv[]){
    	if(argc < 2)
    	{
    		printf("%s arg error.",argv[0]);
    		exit(1);
    	}
    	kill(atoi(argv[1]),SIGKILL);
    	return 0;
    }
    
    

    在另外一个终端完成上面程序的输入并编译,运行的时候把2784作为参数。
    
    再返回之前运行 hellosignal 的终端观察进程接收到信号的反应。
    

    通过手册查看 kill() 函数:
    
    man 2 kill
    

    2.使用 signal() 函数来捕捉信号。

    通常进程在接收到某种信号后,会根据不同的信号执行默认的操作:
    
    (1)忽略信号
    
    (2)终止(杀死)进程
    
    (3)产生核心转储文件,同时 终止进程
    
    (4)停止进程
    
    (5)恢复之前被暂停的进程继续运行
    
    这里可以通过signal()来改变进程对某个信号的处置方式。signal()是目前为止见过最复杂的函数。
    
    //catchsignal.c
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    void sighandler(int sig) {
    	switch(sig) {
    		case SIGUSR1://10
    			printf("hello SIGUSR1
    ");break;
    		case SIGUSR2://12
    			printf("hello SIGUSR2
    ");break;
    		case SIGINT://2 CTRL+C
    			printf("休想干掉我!
    ");break;
    		case SIGTSTP://20 CTRL+Z
    			printf("不要停止我!
    ");break;
    		case SIGQUIT://3 CTRL+
    			printf("就是不退出!
    ");break;
    		case SIGSEGV://11
    			printf("呃!程序出 bug 了!
    ");break;
    		default:
    			printf("hello, who are you %d?
    ", sig);
    	}
    	sleep(2); // 删除这一行,再给程序发信号,看看 main 函数打点的情况。
    }
    int main() {
    	printf("I'm %d
    ", getpid());
    	if (SIG_ERR == signal(SIGUSR1, sighandler)) {
    		perror("signal SIGUSR1");
    	}
    	if (SIG_ERR == signal(SIGUSR2, sighandler)) {
    		perror("signal SIGUSR2");
    	}
    	if (SIG_ERR == signal(SIGINT, sighandler)) {
    		perror("signal SIGINT");
    	}
    	if (SIG_ERR == signal(SIGTSTP, sighandler)) {
    		perror("signal SIGTSTP");
    	}
    	if (SIG_ERR == signal(SIGQUIT, sighandler)) {
    		perror("signal SIGQUTI");
    	}
    	if (SIG_ERR == signal(SIGSEGV, sighandler)) {
    		perror("signal SIGSEGV");
    	}
    	while(1) {
    		write(STDOUT_FILENO, ".", 1);
    		sleep(10);
    	}
    	return 0;
    }
    输入完上述代码,编译并运行。
    
    (1)给该进程发送ctrl+C、ctrl+Z、ctrl+信号,观察进程对接收到信号的反应。
    
    (2)在另外一个终端输入 kill 命令来向该进程发送信号,观察进程对接收到信号的反应。
    

    3.通过举例说明 alarm() 函数和 setitimer() 函数的使用。

    查看两个函数的手册:
    
    (1)man 2 alarm
    (2)man 2 setitimer
    man 2 alarm
    

    man 2 setitimer
    

    通过命令 man 7 signal 可以查看当前系统信号的清单:
    

    从上面可以看到 alarm() 函数在计时结束后会发生 SIGALRM 信号给当前进程,进程对 SIGALRM 信号的缺省动作是结束进程。
    //例:
    //alarm_test.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
     
    int main(){
    	alarm(1);
    	while(1)
    	{
    		printf("process will finish!
    ");
    	}
    	return 0;
    }
    

    虽然程序中有无限循环,不断输出字符串process will finish!,
    
    由于调用了alarm(1)函数alarm函数会在1秒后给该进程发送 SIGALRM 信号,然后进程结束。
    接下来继续看一个程序设定了两次定时炸弹,第一次设定 5 秒后爆炸,设定后过了 2 秒,再设定了一个3 秒后爆炸的定时炸弹。
    //myalarm.c
    #include<stdio.h>
    #include <unistd.h>
    #include <signal.h>
    void handler(int sig) {
    	if (sig == SIGALRM) printf("Bomb!!!!!!!!
    ");
    }
    int main() {
    	if(SIG_ERR == signal(SIGALRM, handler)) {
    		perror("signal SIGALRM");
    	}
    	unsigned int remain = 0;
        remain = alarm(5); // 设定 5 秒后爆炸
        printf("the previous alarm remain %d seconds
    ", remain);
        sleep(3); // 等待 3 秒
    	remain = alarm(3); // 设定 3 秒后爆炸,同时会取消前面那个定时炸弹
    	printf("the previous alarm remain %d seconds
    ", remain);
        while(1) {
    		write(STDOUT_FILENO, ".", 1);
        	pause();
    	}
    }
    

    这里计时时间到了并不会结束进程,因为编写了信号捕捉函数,
    
    产生SIGALRM信号后会输出字符串 Bomb!!,可以键盘按键组合结束进程,这里用了CTRL+C
    
    接下来用 setitimer() 函数实现 alarm() 函数
    //mysetitimer.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/time.h>
    #include<error.h>
    unsigned int my_alarm(unsigned int sec)
    {
    	struct itimerval it,oldit;
    	int ret;
    	it.it_value.tv_sec=sec;
    	it.it_value.tv_usec=0;
    	it.it_interval.tv_sec=0;
    	it.it_interval.tv_usec=0;
    	ret = setitimer( ITIMER_REAL, &it, &oldit);
    	if(ret==-1)
    	{
    		perror("setitimer()");
    		exit(1) ;
    	}
    		return oldit.it_value.tv_sec;
    }
    int main(){
    	my_alarm( 1) ;
    	while(1)
    	{
    		printf( "process will finish! 
    ");
    	}
    	return 0;
    }
    

    程序在运行1秒钟后被 SIGALRM 信号结束。
    试分析一下 alarm() 函数和 setitimer() 函数的区别?
    
    alarm()函数,当定时时间到了,就会产生SIGALRM信号结束进程。
    
    setitimer()函数从开始计时到时间结束,会产生SIGALRM信号,
    
    若it_interval的值为0,则不会重新启动该定时器,调用 setitimer()失败,返回-1,结束进程。
    

    4.举例说明信号集操作函数的使用

    通过命令 man 3 sigsetops 来查看手册:
    

    编写一个打印 sigset_t 的函数
    void printsigset(const sigset_t *set)
     {
    int i;
    for (i = 1; i <= 64; i++) {
    if (i==33)
    putchar(' ');
    if (sigismember(set, i) == 1)
    putchar('1');
    else
    putchar('0');
    }
    puts("");
    }
    //mysigset.c
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    void printsigset(const sigset_t *set)
    {
    	int i;
    	for (i = 1; i <= 64; i++) {
    		if (i==33)
    			putchar(' ');
    		if (sigismember(set, i) == 1)
    			putchar('1');
    		else
    			putchar('0');
    	}
    	puts("");
    }
    int main() {
    	sigset_t st;
    	printf("1. create set
    ");
    	printsigset(&st);
     
    	printf("
    2. vertify sigset_t is a 64-bit integer
    ");
    	unsigned int test[2] = {0xf0f0f0f0, 0xf0f0f0f0};
    	printsigset((sigset_t*)test); // 这种方法不被推荐,仅供测试用。
    	
    	// fill set
    	printf("
    3. fill set
    ");
    	sigfillset(&st);
    	printsigset(&st);
    	
    	// empty set 
    	printf("
    4. empty set
    ");
    	sigemptyset(&st);
    	printsigset(&st); 
    	
    	// add sig
    	printf("
    5. add SIGHUP(1), SIGINT(2), SIGKILL(9), SIGSYS(31), SIGRTMIN(34) and SIGRTMAX(64) to set
    ");
    	sigaddset(&st, SIGHUP);
    	sigaddset(&st, SIGINT);
    	sigaddset(&st, SIGKILL);
    	sigaddset(&st, SIGSYS);
    	sigaddset(&st, SIGRTMIN);
    	sigaddset(&st, SIGRTMAX);
    	printsigset(&st);
     
    	// delete sig
    	printf("
    6. delete SIGKILL from set
    ");
    	sigdelset(&st, SIGKILL);
    	printsigset(&st);
     
    	// is member
    	printf("
    ");
    	if (sigismember(&st, SIGKILL)) {
    	printf("SIGKILL is member
    ");
    	}
    	if (sigismember(&st, SIGINT)) {
    	printf("SIGINT is member
    ");
    	}
    	return 0;
    }
    

    5、举例说明对阻塞信号与未决信号的理解

    在一个进程中,保存了两个信号集(在PCB中),分别是阻塞信号集,还有一个未决信号集。当你使用sigprocmask的时候,就会修改阻塞信号集。
    
    当你的进程一收到信号且该信号被阻塞,它首先进入到未决信号集中(就是一个 sigset_t ),
    
    当未决信号集中的信号被信号处理函数(你自己定义的或者系统默认的)处理,就会从未决信号集中删除。
    
    如果一个信号加入阻塞信号集,该信号的信号处理函数就不会被调用。
    
    通过 man sigprocmask 来查看手册:
    

    对于未决信号集我们不能直接操作,可以使用 sigpending 函数获取未决信号集。
    
    通过 man sigpending 来查看手册:
    

    下面结合例子来理解,程序的功能是先把 SIGINT 、 SIGTSTP 加入到了进程阻塞信号集中去。
    
    接下来,每隔一秒打印一次未决信号集,第 10 次的时候,又把 SIGINT 信号从阻塞信号集中删除。
    //sigblock.c
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    #include <stdlib.h>
    void printsigset(const sigset_t *set)
    {
    	int i;
    	for (i = 1; i <= 64; i++) {
    		if (i==33)
    			putchar(' ');
    		if (sigismember(set, i) == 1)
    			putchar('1');
    		else
    			putchar('0');
    	}
    	puts("");
    }
    
    void handler(int sig) {
    	if (sig == SIGINT)
    		printf("hello SIGINT
    ");
    	if (sig == SIGQUIT)
    		printf("hello SIGQUIT
    ");
     }
     
    int main() {
    	printf("I'm %d
    ", getpid());
    	
    	sigset_t st, oldst;
    	sigemptyset(&st);
    	sigaddset(&st, SIGINT);
    	sigaddset(&st, SIGTSTP);
    	sigprocmask(SIG_BLOCK, &st, &oldst);
    	printf("new set:");
    	printsigset(&st);
    	printf("old set:");
    	printsigset(&oldst);
     
    	if (SIG_ERR == signal(SIGINT, handler)) {
    		perror("signal SIGINT");
    		return 1;
    	}
    	
    	if (SIG_ERR == signal(SIGQUIT, handler)) {
    		perror("signal SIGQUIT");
    		return 1;
    	}
    	
    	puts("");
    	
    	int n = 0;
    
    	while(1) {
    		sigpending(&st);
    		printsigset(&st);
    		puts("");
    		sleep(1);
    		
    		if (n == 10) {
    			sigset_t tmp;
    			sigemptyset(&tmp);
    			sigaddset(&tmp, SIGINT);
    			sigprocmask(SIG_UNBLOCK, &tmp, NULL);
    		}  
    		
    		++n;
    	}
    	return 0;
    }
    

    6、 举例说明 sigaction() 函数的使用

    不同于 signal 函数, sigaction 函数是符合 POSIX 标准的,而 signal 只是 ANSI C 定义的函数。
    
    除了上面的区别外, sigaction 提供了更多的功能。
    
    比如它可以处理带参数的信号,在信号处理的时候,可以屏蔽其它信号等等。
    程序来说明 sigaction() 函数的使用,程序注册了信号 SIGINT 和 SIGTSTP . 需要注意的一点是 sa_mask 被设置为 SIGINT ,
    
    它表示当执行信号处理函数的时候,阻塞信 SIGINT 信号。
    
    在 handler 函数加入了一打印未决信号的功能,以验证执行到 handler 的时候发送 SIGINT 是被阻塞住的。
    //sigaction.c
    #include <stdio.h>
    #include <unistd.h>
    #include <signal.h>
    void printsigset(const sigset_t *set)
    {
    	int i;
    	for (i = 1; i <= 64; i++) {
    		if (i==33)
    			putchar(' ');
    		if (sigismember(set, i) == 1)
    			putchar('1');
    		else
    			putchar('0');
    	}
    	puts("");
    }
     
    void handler(int sig) {
    	if (sig == SIGTSTP)
    		printf("hello SIGTSTP
    ");
    	if (sig == SIGINT)
    		printf("hello SIGINT
    ");
    	sleep(5);
    	sigset_t st;
    	sigpending(&st);
    	printsigset(&st);
    }
    
    int main() {
    	printf("I'm %d
    ", getpid());
    	struct sigaction act, oldact; 
    	act.sa_handler = handler; // 设置普通信号处理函数
     
    	// 向 sa_mask 中添加 SIGINT
    	sigemptyset(&act.sa_mask);
    	sigaddset(&act.sa_mask, SIGINT);
    	act.sa_flags = 0; // 先置 0
     
    	sigaction(SIGTSTP, &act, &oldact);
    	sigaction(SIGINT, &act, &oldact);
     
    	while(1) {
    		write(STDOUT_FILENO, ".", 1);
    		pause();
    	}
    	return 0;
    }
    
    

    (1) 当程序运行的时候,Ctrl+C 进入 handler ,然后立即 Ctrl+Z 发现handler还未执行完就被SIGTSTP打断.
    
    (2) 当程序运行的时候,Ctrl+Z 进入 handler ,然后立即 Ctrl+C 发现并不会被 SIGINT 打断,
    
    这是因为该 handler 注册的时候被设置了 SA_MASK = SIGINT ,
    
    最后 handler 结束的时候打印了未决信号集,发现里头有 SIGINT,所以 handler 结束后,又去继续对 SIGINT 进行处理。
    
  • 相关阅读:
    在C#代码中应用Log4Net(二)典型的使用方式
    在C#代码中应用Log4Net(一)简单使用Log4Net
    Windows Azure Active Directory (2) Windows Azure AD基础
    Windows Azure Virtual Network (6) 设置Azure Virtual Machine固定公网IP (Virtual IP Address, VIP) (1)
    Windows Azure Active Directory (1) 前言
    Azure China (6) SAP 应用在华登陆 Windows Azure 公有云
    Microsoft Azure News(3) Azure新的基本实例上线 (Basic Virtual Machine)
    Microsoft Azure News(2) 在Microsoft Azure上运行SAP应用程序
    Microsoft Azure News(1) 新的数据中心Japan East, Japan West and Brazil South
    Windows Azure HandBook (2) Azure China提供的服务
  • 原文地址:https://www.cnblogs.com/lc2333/p/12932847.html
Copyright © 2011-2022 走看看