UNIX环境高级编程学习笔记八

    科技2026-06-15  1

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

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

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

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

    首先,找到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

    再编写代码读取上面保存的sig1.txt和sig2.txt文件,获取其中的signal和signum的关系并将其保存于字符串数组中最后循环读取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的其他调度导致的延时。

    Processed: 0.018, SQL: 9