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的其他调度导致的延时。