博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
APUE笔记七
阅读量:6481 次
发布时间:2019-06-23

本文共 10961 字,大约阅读时间需要 36 分钟。

  hot3.png

1. 信号基础

    信号是软件中断,提供了一种处理异步事件的方法。由于产生信号的事件对进程而言是随机出现的,所以进程不能简单的测试一个变量来判别是否出现了一个信号,而是必须告诉内核“在此信号出现时,请执行以下操作”。一般按以下三种方式操作:

1) 忽略此信号。

2) 捕捉信号

3) 执行系统默认动作。

2. signal函数

简单的signal接口定义如下:

void (*signal(int signo, void (*func)(int)))(int);
    signo参数是信号名。func的值是常量SIG_IGN,SIG_DFL或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示 忽略此信号。如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,则在信号发生时,调用此函数(信号处理函数)
#include 
#include
static void sig_usr( int );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"); for ( ; ; ) pause(); return 0;}static void sig_usr( int signo ){ if ( signo == SIGUSR1 ) printf("received SIGUSR1\n"); else if ( signo == SIGUSR2 ) printf("received SIGUSR2\n"); else{ printf("received signal %d\n", signo ); }}
程序输出:

3. 中断的系统调用

    早期UNIX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其error被设置为EINTR。低速系统调用是可能会使进程永远阻塞的一类系统调用,它们包括:

1) 在读某些类型的文件(管道,终端设备以及网络设备)时,如果数据并不存在则可能会使调用者永远阻塞。

2) 在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞。

3) 打开某些类型文件,在某种条件发生之前也可能会使调用者阻塞。

4) pause和wait函数

5) 某些ioctl操作

6) 某些进程间通信函数

    与被中断的系统调用相关的问题是必须显式的处理出错返回。

4. 可重入函数

    不可重入函数的含义是:如果信号处理程序和主程序中调用相同的函数,则会造成内部指针出错。通常不可重入的函数原因如下:1)已知它们使用静态数据结构。2)它们调用malloc或free。3)它们是标准I/O函数。

#include 
#include
#include
static void my_alarm( int signo ){ struct passwd *rootptr; printf("in signal handler\n"); if ( ( rootptr = getpwnam( "root" ) ) == NULL ) printf("getpwnam(root) error\n"); alarm( 1 );}int main( void ){ struct passwd *ptr; signal( SIGALRM, my_alarm ); alarm(1);// sleep( 2 ); printf("out signal handler\n"); for( ; ; ){ if (( ptr = getpwnam("leichaojian")) == NULL) printf("getpwnam error\n"); if ( strcmp(ptr->pw_name,"leichaojian") != 0 ) printf("return value corrupted! pw_name=%s\n", ptr->pw_name); } return 0;}
    正常情况下,程序应该每个一秒输出:in signal handler,但实际上却是中断,停留在终端阻塞上了:

5. SIGCLD语义

    对于SIGCLD早期处理如下:

1) 如果进程特地的设置该信号的配置为SIG_IGN,则调用进程的子进程将不会产生僵死进程。

2) 如果将SIGCLD的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果有则调用SIGCLD处理程序。

6. kill和raise函数

    kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。

#include 
int kill( pid_t pid, int signo );int raise( int signo );
raise( signo ) == kill( getpid(), signo )

kill的pid参数有4种不同的情况:

1) pid>0: 将该信号发送给进程ID为pid的进程

2) pid==0: 将该信号发送给与发送进程属于同一进程组的所有进程,而且发送进程具有向这些进程发送信号的权限。

3) pid<0: 将该信号发送给其进程组ID等于的绝对值,而且发送进程具有向其发送信号的权限。

4) pid==-1: 将该信号发送给发送进程有权限向它们发送信号的系统上的所有进程。

7. alarm和pause函数

    使用alarm函数可以设置一个计时器,在将来某个特定的时间该计时器会超时。当计时器超时时,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。

1) sleep的简单而不完整的实现

#include 
#include
static void sig_alrm( int signo ){}unsigned int sleep1( unsigned int nsecs ){ if ( signal( SIGALRM, sig_alrm ) == SIG_ERR ) return ( nsecs ); alarm( nsecs ); pause(); return ( alarm( 0 ) );//turn off timer,return unslept time}~
    此函数有以下三个问题:

1) 如果在调用sleep1之前,调用者已设置了闹钟,则它会被sleep1函数中的第一次alarm调用擦除。

2) 该程序中修改了对SIGALRM的配置。如果编写了一个函数供其他函数调用,则在该函数被调用时先要保存原配置,在该函数返回前再恢复原配置。

3) 在第一次调用alarm和调用pause之间有一个竞争条件。在繁忙的系统种可能在pause调用之前超时了,则调用者永远被挂起。

改进版:

#include 
#include
#include
#include
static jmp_buf env_alrm;static void sig_alrm( int signo ){ printf("begin\n"); longjmp( env_alrm, 1 ); printf("end\n");}unsigned int sleep2( unsigned int );static void sig_int( int );int main( void ){ unsigned int unslept; if ( signal( SIGINT, sig_int ) == SIG_ERR ) printf("signal error\n"); unslept = sleep2( 5 ); printf("sleep2 returned:%u\n", unslept ); exit( 0 );}unsigned int sleep2( unsigned int nsecs ){ if ( signal( SIGALRM, sig_alrm ) == SIG_ERR ) return ( nsecs ); if ( setjmp( env_alrm ) == 0 ){ alarm( nsecs ); printf("pause execute!\n" ); pause(); } return ( alarm( 0 ) );}static void sig_int( int signo ){ int i, j; volatile int k; printf("\nsig_int starting\n"); for ( i = 0; i < 300000; i++ ) for ( j = 0; j < 40000; j++ ) k += i * j; printf("sig_int finished\n");}
    注意:sleep2中,就算alarm和pause之间存在竞争,并且超时,我们也可以通过longjmp来跳出来。程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.outpause execute!beginsleep2 returned:0
    alarm可用于对可能阻塞的操作设置时间上限值:
#include 
#include
#include
#define MAXLINE 4096static void sig_alrm( int );int main( void ){ int n; char line[ MAXLINE ]; if ( signal( SIGALRM, sig_alrm ) == SIG_ERR ) printf("signal error\n"); alarm( 10 ); if ( ( n = read( STDIN_FILENO, line, MAXLINE ) ) < 0 ) printf( "read error\n" ); alarm( 0 ); write( STDOUT_FILENO, line, n ); exit( 0 );}static void sig_alrm( int signo ){ printf("one signal\n");}
    如果在alarm和read之间存在竞争条件,并且阻塞了,则read可能永远阻塞。

改进版:

#include 
#include
#include
#include
#define MAXLINE 4096static void sig_alrm( int );static jmp_buf env_alrm;int main( void ){ int n; char line[ MAXLINE ]; if ( signal(SIGALRM, sig_alrm) == SIG_ERR ) printf("signal error\n"); if ( setjmp( env_alrm) != 0) printf("read timeout"); alarm(10); if ( ( n = read( STDIN_FILENO, line, MAXLINE)) < 0) printf("read error\n"); alarm(0); write(STDOUT_FILENO, line, n); exit(0);}static void sig_alrm( int signo ){ longjmp( env_alrm, 1 );}

8. 信号集

    五个处理信号集的函数:

#include 
int sigemptyset( sigset_t *set ); //清除set中所有信号int sigfillset( sigset_t *set ); //初始化set指向的信号集int sigaddset( sigset_t *set, int signo ); //增加信号int sigdelset( sigset_t *set, int signo ); //删除信号int sigismember( const sigset_t *set, int signo ); //判断是否存在signo信号
#include 
#include
#include
static void sig_quit( int );int main( void ){ sigset_t newmask, oldmask, pendmask; if ( signal( SIGQUIT, sig_quit ) == SIG_ERR ) printf( "can't catch SIGQUIT" ); sigemptyset( &newmask ); sigaddset( &newmask, SIGQUIT ); //将SIGQUIT信号增加到新的信号集中 //保留旧信号集 if ( sigprocmask( SIG_BLOCK, &newmask, &oldmask ) < 0 ) printf( "SIG_BLOCK error" ); sleep( 5 ); //返回新的信号集 if ( sigpending( &pendmask ) < 0 ) printf( "sigpending error" ); //判断新的信号集中是否有SIGQUIT信号 if ( sigismember( &pendmask, SIGQUIT ) ) printf("\nSIGQUIT pending\n" ); //重新使用旧的信号集 if ( sigprocmask( SIG_SETMASK, &oldmask, NULL ) < 0 ) printf("SIG_SETMASK error"); printf("SIGQUIT unblocked\n"); sleep( 5 ); exit( 0 );}static void sig_quit( int signo ){ printf("caught SIGQUIT!\n"); if ( signal( SIGQUIT, SIG_DFL ) == SIG_ERR ) printf("can't reset SIGQUIT");}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out^\^\SIGQUIT pendingcaught SIGQUIT!SIGQUIT unblocked
9. sigsetjmp和siglongjmp函数

    可恢复被屏蔽的信号

#include 
#include
#include
#include
#include
static void sig_usr1( int ), sig_alrm( int );static sigjmp_buf jmpbuf;static volatile sig_atomic_t canjump;int main( void ){ if ( signal( SIGUSR1, sig_usr1 ) == SIG_ERR ) printf("signal SIGUSR1 error\n"); if ( signal( SIGALRM, sig_alrm ) == SIG_ERR ) printf("signal SIGALRM error\n"); printf("starting main:\n"); if ( sigsetjmp( jmpbuf, 1)){ printf("ending main:\n"); exit( 0 ); } canjump = 1; for ( ; ; ) pause();}static void sig_usr1( int signo ){ time_t starttime; if ( 0 == canjump ) return; printf("starting sig_usr1:\n"); alarm( 3 ); starttime = time( NULL ); for ( ; ; ) if ( time( NULL ) > starttime + 5 ) break; printf("finishing sig_usr1\n"); canjump = 0; siglongjmp( jmpbuf, 1 );}
程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.out &[2] 3890leichaojian@ThinkPad-T430i:~$ starting main:kill -USR1 3890starting sig_usr1:leichaojian@ThinkPad-T430i:~$ in sig_alrm: finishing sig_usr1ending main:[2]-  Done                    ./a.out
10. sigsuspend函数
#include 
int sigsuspend( const sigset_t *sigmask );
    将进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信 号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。

1) 保护临界区不被信号中断

    以下程序保证SIGINT信号被悬挂起来。

#include 
#include
#include
void pr_mask( const char *str ){ sigset_t sigset; int errno_save; errno_save = errno; if ( sigprocmask( 0, NULL, &sigset ) < 0 ) printf("sigprocmask error\n"); printf("%s", str); if ( sigismember( &sigset, SIGINT)) printf("SIGINT "); if (sigismember( &sigset, SIGQUIT)) printf("SIGQUIT "); if (sigismember( &sigset, SIGUSR1)) printf("SIGUSR1" ); if (sigismember(&sigset, SIGALRM)) printf("SIGALRM"); printf("\n"); errno = errno_save;}static void sig_int( int );int main( void ){ sigset_t newmask, oldmask, waitmask; pr_mask( "program start:" ); if ( signal( SIGINT, sig_int ) == SIG_ERR ) printf("signal(SIGINT) error\n"); sigemptyset( &waitmask ); sigaddset(&waitmask, SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask, SIGINT ); if ( sigprocmask( SIG_BLOCK, &newmask, &oldmask) < 0) printf("SIG_BLOCK error"); pr_mask("in critical region:"); if ( sigsuspend( &waitmask) != -1 ) printf("sigsuspend error\n"); pr_mask("after return from sigsuspend\n"); if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) printf("SIG_SETMASK error\n"); pr_mask("program exit:"); exit(0);}static void sig_int( int signo ){ pr_mask("\nin sig_int:");}
    程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.outprogram start:in critical region:SIGINT ^Cin sig_int:SIGINT SIGUSR1after return from sigsuspendSIGINT program exit:
2) 用sigsuspend等待一个全局变量被设置
#include 
#include
volatile sig_atomic_t quitflag;static void sig_int( int signo ){ if ( signo == SIGINT ) printf("\ninterrupt\n"); else if ( signo == SIGQUIT ) quitflag = 1;}int main( void ){ sigset_t newmask, oldmask, zeromask; if ( signal( SIGINT, sig_int ) == SIG_ERR ) printf("signal (SIGINT) error"); if ( signal( SIGQUIT, sig_int ) == SIG_ERR ) printf("signal (SIGQUIT) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) printf("SIG_BLOCK error\n"); printf("block begin:\n"); while ( quitflag == 0 ) sigsuspend(&zeromask); printf("block end\n"); quitflag = 0; if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) printf("SIG_SETMASK error\n"); exit( 0 );}
    程序输出:
leichaojian@ThinkPad-T430i:~$ ./a.outblock begin:^Cinterrupt^Cinterrupt^\block end

转载于:https://my.oschina.net/voler/blog/337347

你可能感兴趣的文章
IT人员的职业生涯规划
查看>>
sorry,you must have a tty to run sudo
查看>>
ios开发中使用正则表达式识别处理字符串中的URL
查看>>
项目中的积累,及常见小问题
查看>>
Python类型转换、数值操作(收藏)
查看>>
oracle11g dataguard 安装手册(转)
查看>>
1. Two Sum - Easy - Leetcode解题报告
查看>>
多线程---同步函数的锁是this(转载)
查看>>
百练 2742 统计字符数 解题报告
查看>>
Ubuntu搜狗输入法候选词乱码
查看>>
js中回调函数写法
查看>>
React native android 最常见的10个问题
查看>>
数据结构和算法
查看>>
[pat]1045 Favorite Color Stripe
查看>>
Immutable学习及 React 中的实践
查看>>
【转】性能测试步骤
查看>>
OSI与TCP/IP各层的结构与功能,都有哪些协议
查看>>
Android实例-程序切换到后台及从后台切换到前台
查看>>
spring boot启动定时任务
查看>>
算法 (二分查找算法)
查看>>