UNIX环境高级编程学习笔记八_信号部分习题

本文探讨了信号处理机制在进程间的应用,包括信号处理函数的设计、进程间通过信号实现同步的方法,以及如何利用信号避免进程提前终止。此外,还讨论了定时器的实现和信号与超时处理的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

10.1 删除10-2程序中for( ; ; )语句结果

for()语句保证进程能持续接收信号,如果不设置for(),第一次接收到信号后进程即将终止。

10.2 实现10.22节中的sig2str()函数

以下代码为实现sig2str函数提供了一个思路,但是未封装成一个函数,仅以程序来实现相同的功能。以下为实现思路:

  1. 首先,找到signal和signo的对应关系,即找到Linux下的signum.h和signum_generic.h文件。我的系统为ubuntu18,其文件在以下两个目录;我将其复制到当前目录下并重命名为sig1.txt和sig2.txt以方便写程序读取。

/usr/include/x86_64-linux-gnu/bits/signum.h
/usr/include/x86_64-linux-gnu/bits/signum-generic.h

  1. 再编写代码读取上面保存的sig1.txt和sig2.txt文件,获取其中的signal和signum的关系并将其保存于字符串数组中
  2. 最后循环读取shell输入的数字,输出其信号名字符串
    代码如下:
#include"apue.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>

#define Len 100
#define Siglen 10
#define sig_count 200

int sigstr_num(char *p, char *sig);

int main()
{
    FILE *fp;
    _Bool num_flag[sig_count]={false};
    char str[Len], sig[Siglen];
    char num_str[sig_count][Siglen];
    char input_str[3]={'\0'};
    int signu, i;
    fp = fopen("./sig2.txt", "r");
    if(fp == NULL)
        err_sys("Open file sig2.txt error!");
    while(fgets(str, Len, fp) != NULL){        /*get all the signal from file*/
        /*printf("Sig2 Str: %s\n", str);*/
        if((signu = sigstr_num(str, sig)) == 0){
            memset(str, '\0', sizeof(str));
            memset(sig, '\0', sizeof(sig));
            continue;
        }
        for(i=0; i<strlen(sig); i++)
            num_str[signu][i] = sig[i];
        num_flag[signu] = true;
    }
    fclose(fp);
    /* Got the signal and it's number */
    for(i=0; i<sig_count; i++)
        if(num_flag[signu])
            printf("%s: \t %d\n", num_str[i], i);
    while(1){
        printf("Please input signo:(Input q to quit)\n");
        scanf("%s", input_str);
        if(input_str[0] == 'q' || input_str[0] == 'Q')
            return(0);
        if((signu = atoi(input_str)) == 0)
            continue;
        if(num_flag[signu])
            printf("The signo of %s is %d\n", num_str[signu], signu); 
        else
            printf("There is no signo of %d, please input correct number!\n", signu);
    }
}

int sigstr_num(char *p, char *sig)
{
    _Bool tab_flag = false;           /*Whether str after space*/
    _Bool second_in = false;          /*whether the first str is got*/
    int i,j=0, k=0;                         
    char str[3][Siglen];              /*store the str msg*/
    for(i=0; i<strlen(p); i++){      /*loop for finding str*/
        if((p[i] == 35 || (p[i]<58&&p[i]>47) || (p[i]<91&&p[i]>64) || (p[i]<123&&p[i]>96) ) == 0){             /*if it is no use char, jump to next loop*/
            if(second_in && k!=0)
                str[j][k] = '\0';
            tab_flag = true;
            k=0;
            if(p[i] != '\n')         /*avoid the case that the '\n' char is just following the number*/
                continue;
        }
        if(tab_flag && second_in){
           /* printf("j: %d, str=%s \n", j, str[j]);*/
            switch(j){
                case 0:                /*first str*/
                    if(strcmp(str[j],"#define"))   
                        return(0);
                    break;
                case 1:                /*second str*/
                    if(str[j][0]=='S' && str[j][1]=='I' && str[j][2]=='G')
                        strcpy(sig, str[j]); /*copy str*/
                    else
                        return(0);
                    break;
                case 2:                /*str to num*/
                    return(atoi(str[j]));
                    break;
            }
            if(j++>2)                  /*get three str*/
                return(0);
        }
        second_in = true;          
        tab_flag = false;              /*reset tab_flag*/
        str[j][k++] = p[i];
    }
    return(0);
}

执行结果如下所示:
代码程序执行结果

对应signum-generic.h文件内容如下:


#ifndef	_BITS_SIGNUM_GENERIC_H
#define _BITS_SIGNUM_GENERIC_H 1

#ifndef _SIGNAL_H
#error "Never include <bits/signum-generic.h> directly; use <signal.h> instead."
#endif

/* Fake signal functions.  */

#define	SIG_ERR	 ((__sighandler_t) -1)	/* Error return.  */
#define	SIG_DFL	 ((__sighandler_t)  0)	/* Default action.  */
#define	SIG_IGN	 ((__sighandler_t)  1)	/* Ignore signal.  */

#ifdef __USE_XOPEN
# define SIG_HOLD ((__sighandler_t) 2)	/* Add signal to hold mask.  */
#endif


/* ISO C99 signals.  */
#define	SIGINT		2	/* Interactive attention signal.  */
#define	SIGILL		4	/* Illegal instruction.  */
#define	SIGABRT		6	/* Abnormal termination.  */
#define	SIGFPE		8	/* Erroneous arithmetic operation.  */
#define	SIGSEGV		11	/* Invalid access to storage.  */
#define	SIGTERM		15	/* Termination request.  */

/* Historical signals specified by POSIX. */
#define	SIGHUP		1	/* Hangup.  */
#define	SIGQUIT		3	/* Quit.  */
#define	SIGTRAP		5	/* Trace/breakpoint trap.  */
#define	SIGKILL		9	/* Killed.  */
#define SIGBUS		10	/* Bus error.  */
#define	SIGSYS		12	/* Bad system call.  */
#define	SIGPIPE		13	/* Broken pipe.  */
#define	SIGALRM		14	/* Alarm clock.  */

/* New(er) POSIX signals (1003.1-2008, 1003.1-2013).  */
#define	SIGURG		16	/* Urgent data is available at a socket.  */
#define	SIGSTOP		17	/* Stop, unblockable.  */
#define	SIGTSTP		18	/* Keyboard stop.  */
#define	SIGCONT		19	/* Continue.  */
#define	SIGCHLD		20	/* Child terminated or stopped.  */
#define	SIGTTIN		21	/* Background read from control terminal.  */
#define	SIGTTOU		22	/* Background write to control terminal.  */
#define	SIGPOLL		23	/* Pollable event occurred (System V).  */
#define	SIGXCPU		24	/* CPU time limit exceeded.  */
#define	SIGXFSZ		25	/* File size limit exceeded.  */
#define	SIGVTALRM	26	/* Virtual timer expired.  */
#define	SIGPROF		27	/* Profiling timer expired.  */
#define	SIGUSR1		30	/* User-defined signal 1.  */
#define	SIGUSR2		31	/* User-defined signal 2.  */

/* Nonstandard signals found in all modern POSIX systems
   (including both BSD and Linux).  */
#define	SIGWINCH	28	/* Window size change (4.3 BSD, Sun).  */

/* Archaic names for compatibility.  */
#define	SIGIO		SIGPOLL	/* I/O now possible (4.2 BSD).  */
#define	SIGIOT		SIGABRT	/* IOT instruction, abort() on a PDP-11.  */
#define	SIGCLD		SIGCHLD	/* Old System V name */

#define __SIGRTMIN	32
#define __SIGRTMAX	__SIGRTMIN

/* Biggest signal number + 1 (including real-time signals).  */
#define _NSIG		(__SIGRTMAX + 1)

#endif /* bits/signum-generic.h.  */

10.4 图10-11程序中利用setjmp和longjmp设置I/O操作的超时,下面代码也用于此目的:

signal(SIGALRM,sig_alrm);
alarm(60);
if(setjmp(env_alrm) != 0){
  /* handle timeout */
  ...
}
...

这代码的错误在哪?
如果在alarm()和setjmp()之间发生竞争条件,在程序未运行至setjmp()前若已经调用了SIGALRM信号,此时setjmp()调用时env_alrm变量未定义,会出现一些问题。

10.5 仅使用一个定时器构造一组函数,使得进程在单一定时器基础上实现任意数量的定时器:

Implementing Software Timers 论文

10.6 编写一段程序测试图10-24中父子进程的同步函数,要求创建一个文件,并向其中写整数0,然后调用fork(),父子进程依次给函数值进行加一操作,并打印哪个进程进行的操作。

首先编写父子进程同步函数,代码如下(为与系统中已有同步函数区分,这里的同步函数都以 ‘1’ 结尾)

#include"apue.h"

static volatile sig_atomic_t sigflag;
static sigset_t newmask, oldmask, zeromask;

static void sig_usr(int signo)             /*信号触发函数*/
{
    if(signo == SIGUSR1)
        printf("SIGUSR1 used!\n");
    else if(signo == SIGUSR2)
        printf("SIGUSR2 used!\n");
    sigflag = 1;
}

void TELL_WAIT1(void)             /*信号操操作函数实现*/
{
    if(signal(SIGUSR1, sig_usr) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");
    if(signal(SIGUSR2, sig_usr) == SIG_ERR)
        err_sys("signal(SIGUSR2) error");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGUSR1);
    sigaddset(&newmask, SIGUSR2);

    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        err_sys("SIG_BLOCK error");
}

void TELL_PARENT1(pid_t pid)
{
    kill(pid, SIGUSR2);
}
void WAIT_PARENT1(void)
{
    while(sigflag == 0)
        sigsuspend(&zeromask);
    sigflag = 0;
    if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASK error");
}

void TELL_CHILD1(pid_t pid)
{
    kill(pid, SIGUSR1);
}
void WAIT_CHILD1(void)
{
    while(sigflag == 0)
        sigsuspend(&zeromask);
    sigflag = 0;
    if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        err_sys("SIG_SETMASKS error");
}

这里与10-24中代码不同的是,其中打印了调用信号的信息,为了便于了解程序的具体运行过程。

我们在主函数中调用这些函数,实现进程的同步。这里涉及到如何编写Makefile在Unix类系统下引用C文件,apue.3e的signal章节中的Makefile文件的介绍我们在下面这篇文章介绍:

主程序运行时,父进程创建文件alterfile.txt,然后父子进程依次写文件。并增加文件中的计数值。以下为主程序代码:

#include"apue.h"
#include<string.h>
#include<stdlib.h>

#define strl 50

void TELL_WAIT1(void);
void TELL_PARENT1(pid_t pid);
void WAIT_PARENT1(void);
void TELL_CHILD1(pid_t pid);
void WAIT_CHILD1(void);

int main(void)
{
    FILE *fp;
    pid_t pid;
    char str[strl] = "The start num is 0\n";
    int i, j=0, re;
    fp = fopen("./alterfile.txt", "w+");
    if(fp == NULL)
        err_sys("File creat error!");
    if(fputs(str, fp) == EOF)
        err_sys("Wirte file error!");
    fflush(fp);                /*冲刷缓冲区*/
    TELL_WAIT1();
    if((pid=fork()) < 0){
        err_sys("Pid fork() error!");
    }
    else if(pid == 0){
            WAIT_PARENT1();
            for(i=0; i<10; i++){
                printf("child i: %d\n", i);
                j = i*2 + 1 ;
                sprintf(str, "This is child, update num: %d\n", j);
                if((re=fputs(str, fp)) == EOF)
                    err_sys("Child write file error!");
                fflush(fp);
                memset(str, '\0', strlen(str));
                TELL_PARENT1(getppid());
                WAIT_PARENT1();
             }
            exit(0);
        }else{
            for(i=0; i<10; i++){
                printf("parent i: %d\n", i);
                j = i*2;
                sprintf(str, "This is father, update num: %d\n", j);
                if((re=fputs(str, fp)) == EOF)
                    err_sys("Father write file error!");
                fflush(fp);
                memset(str, '\0', strlen(str));
                TELL_CHILD1(pid);
                WAIT_CHILD1();
            }
        }
    fclose(fp);
    exit(0);
}

以上程序比较结构比较简单,这里不进行详细介绍。这里主要强调下每次fputs()函数调用后的 fflush() 函数。在子进程调用fputs()函数后必须马上进行流冲刷,否则父进程下一次调用fputs()时将会覆盖掉子进程的流(这个问题困扰了我半天,我一度以为是父子进程不能操作同一文件…),导致文件中仅存在父进程写好的信息。还有就是程序开始时写入开始信息后也需要进行流冲刷,否则父进程调用子进程后,子进程冲刷时会导致这个流重复写入文件中。

  • 但这里还存在一个问题,即文件指针fp在两个进程中的指向是如何同步的?有空再研究下

执行结果如下:
在这里插入图片描述

10.8 为什么在siginfo结构的si_uid字段中包括实际用户ID而非有效用户ID?

实际用户为信号的接收者提供了更多信息,如谁调用之类的。

10.9 重写10-14函数,要求处理10-1中所有信号,每次处理当前屏蔽中的一个信号。

本代码和10.2类似,只需要把10.2代码中得到的数据代入到pr_mask()函数的sigismember()中即可。

10.10 在无限循环中调用sleep(60)并打印当前程序的时间,长时间后观察时间是否对得上。再研究为什么会发生偏差?

代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include"apue.h"

void prt_time(void);
int main()
{
    int i=0;
    while(1){
        i++;
        sleep(60);
        printf(" %d minutes passed\n", i);
        prt_time();
    }
    return 0;
}
void prt_time(void)
{
    time_t t;
    struct tm *tmp;
    char buf1[16];
    char buf2[64];

    time(&t);
    tmp = localtime(&t);
   /* if(strftime(buf1, 16, "time and date: %r, %a %b %d, %Y", tmp) == 0)
        printf("buffer length 16 is too small!");*/
    if(strftime(buf2, 64, "time and date: %r, %a %b %d, %Y", tmp) == 0)
        printf("buffer length 64 is too small!");
    else
        printf("%s\n", buf2);
}

为尽快的得到时间偏差,本程序循环时间由5分钟改为了1分钟。代码程序运行如下:
时间验证
由以上的结果可知,每经过10分钟,sleep(60)的休眠时间和系统时间将产生1s的偏差。分析产生的时间误差的原因可以归为两类,一类是调用程序所耗费的时间;另一类由于cpu的其他调度导致的延时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值