如何在C/Linux中的计时器到期后执行代码post阻塞操作?
How to execute code post blocking operation after expiry of timer in C/Linux?
我有一种情况需要在阻塞操作之前启动到期计时器。如果控制在预定的时间间隔内没有从阻塞操作中出来,我需要继续前进并恢复其他活动。该程序不应在计时器到期时终止。相反,我必须执行阻塞操作之后的代码。在下面的代码片段中,当计时器到期时,我需要去 B 点而不是退出。工作环境为ubuntu18.04,代码为c。我探索了 setitimer
函数调用以及与信号处理程序相关联的函数调用。但是控制并没有跳过阻塞操作并继续执行后续代码。
Program to create a semaphore and setting the semaphore for use by another program.
int main(int argc,char *argv[])
{
key_t SemKey = 0x12345 ;
int SemId = -1;
int NumOfSems = 2 ;
int Flag ;
int RetStat ;
char choice = 'n' ;
struct
{
int val;
struct semid_ds *buf;
ushort array[2] ;
} Arg ;
Flag = IPC_CREAT | IPC_EXCL | 0666 ;
SemId = semget(SemKey,NumOfSems,Flag ) ;
if ( SemId == -1 )
{
perror("semget");
exit(EXIT_FAILURE);
}
Arg.array[0] = 0 ;
Arg.array[1] = 0 ;
RetStat = semctl(SemId, 0, SETALL, Arg.array);
if (RetStat == -1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
printf("\nPress y or Y to continue ahead.\n");
scanf("%c",&choice);
if ( ( choice != 'y') && ( choice != 'Y'))
exit(EXIT_FAILURE);
if ( ( choice != 'y') && ( choice != 'Y'))
exit(EXIT_FAILURE);
RetStat = semctl( SemId,0, SETVAL, 1 );
if( RetStat < 0 )
{
perror( "SET SEMOP Failure: " );
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS ;
}
Program that waits for the semaphore set by the above program.
int DriverModule(int );
int main(int argc,char *argv[])
{
key_t SemKey = 0x12345 ;
int SemId ;
u_int Flag;
Flag = 0666 ;
SemId = semget( SemKey, 0, Flag );
if ( SemId == -1 )
{
perror("semget");
exit(EXIT_FAILURE);
}
DriverModule(SemId) ;
return EXIT_SUCCESS ;
}
#define MAX_ITERATIONS 100
struct itimerval timer;
int WaitForSemaphore(int ,unsigned short ) ;
void timer_handler (int signum)
{
static int count = 0;
printf ("timer expired %d times\n", ++count);
if ( count >= MAX_ITERATIONS ) // Stop the timer
{
printf("\n\nForcing Time Out Termination.....\n\n");
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
setitimer (ITIMER_VIRTUAL, &timer, NULL);
return ;
}
}
int DriverModule (int SemId)
{
struct sigaction sa;
/* Install timer_handler as the signal handler for SIGVTALRM. */
memset (&sa, 0, sizeof (sa));
sa.sa_flags = SA_SIGINFO;
sa.sa_handler = &timer_handler;
sigaction (SIGVTALRM, &sa, NULL);
/* Configure the timer to expire after 250 msec... */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 250000;
/* ... and every 500 msec after that. */
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 500000;
/* Start a virtual timer. It counts down whenever this process is
executing. */
setitimer (ITIMER_VIRTUAL, &timer, NULL);
printf("\nBefore calling wait for semaphore.......\n");
// Waiting for sempahore
if( !WaitForSemaphore( SemId, 0) )
{
printf("\nUnable to get sempahore.\n");
return 0 ;
}
printf("\nAfter calling after calling wait for semaphoe module.........\n");
return 1 ;
}
int WaitForSemaphore(int SemId,unsigned short SemNum )
{
struct sembuf SemBuf;
int RetStat;
unsigned int NoOfSemOps;
SemBuf.sem_num = SemNum;
SemBuf.sem_op = -1;
SemBuf.sem_flg = 0;
NoOfSemOps = 1;
RetStat = semop( SemId, &SemBuf, NoOfSemOps );
if( RetStat < 0 )
{
if( errno == EINTR )
{
WaitForSemaphore( SemId, SemNum );
}
else
{
perror( "Wait SEMOP Failure: " );
return 0 ;
}
}
return 1 ;
}
好的,主要问题是使用 ITIMER_VIRTUAL
。当进程为 运行 时,此 [类型] 计数器 仅 递减。如果进程正在执行系统调用,则它 不是 运行。
因此,我们必须改用 ITIMER_REAL
。而且,如果我们这样做,它会生成一个 SIGALRM
信号 [并且 而不是 一个 SIGVTALRM
信号。
进行这些更改后,出现了另一个问题。
定时器必须被禁用在它正在保护的系统调用之后(例如semop
)。否则,unexpired 计时器(即 semop
确实 not 超时)可能会中断 another/subsequent/unrelated 系统调用(例如 read
、write
、等等)。
因此,在下面的代码中,我们需要(例如):
timer_set(250000);
semop(...);
timer_set(0);
另外,请注意,可以从信号处理程序[安全]执行有限 系统调用[and/or 库函数调用]。值得注意的是,printf
不能 不能 用于信号处理程序。
而且,即使进行“信号安全”系统调用也需要信号处理程序 [在进入时] 保存并在 [退出时] 恢复 errno
的原始值。
否则,基线代码(例如 [中断] semop
之后)将看不到正确的 errno
值(例如 EINTR
),而是 errno
值对于信号处理程序选择执行的任何系统调用。
我不得不对代码进行相当多的重构才能制作出合理的测试程序。
我将这两个程序组合成一个测试程序,以便为正常情况和超时情况生成适当的单元测试,由于计时问题和竞争条件,这对于它们作为单独的程序来说很难。
我还增强了调试打印输出。
无论如何,这是代码:
// create a semaphore and setting the semaphore for use by another program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/time.h>
#include <sys/wait.h>
int opt_n;
int opt_y;
#define MAX_ITERATIONS 100
int itype;
struct itimerval timer;
const char *pgmtail;
int WaitForSemaphore(int, unsigned short);
int DriverModule(int);
typedef long long tsc_t;
tsc_t tvzero;
tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
clock_gettime(CLOCK_MONOTONIC,&ts);
tsc = ts.tv_sec;
tsc *= 1000000000;
tsc += ts.tv_nsec;
tsc -= tvzero;
return tsc;
}
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= 1e9;
return sec;
}
void
dbgprt(const char *fmt,...)
{
va_list ap;
char buf[1000];
char *bp = buf;
bp += sprintf(bp,"%.9f %s ",tscsec(tscget()),pgmtail);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
fputs(buf,stdout);
fflush(stdout);
}
int
pgma(void)
{
key_t SemKey = 0x12345;
pgmtail = "pgma";
int SemId = -1;
int NumOfSems = 2;
int Flag = 0;
int RetStat;
#if 0
char choice = 'n';
#endif
#if 0
struct {
int val;
struct semid_ds *buf;
ushort array[2];
} Arg;
#endif
Flag |= IPC_CREAT;
//Flag |= IPC_EXCL;
Flag |= 0666;
SemId = semget(SemKey, NumOfSems, Flag);
if (SemId == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
#if 0
Arg.array[0] = 0;
Arg.array[1] = 0;
RetStat = semctl(SemId, 0, SETALL, Arg.array);
if (RetStat == -1) {
perror("semctl");
exit(EXIT_FAILURE);
}
#endif
for (int phase = 0; phase <= opt_y; ++phase) {
int setval = phase;
dbgprt("pgma: SET phase=%d\n",phase);
RetStat = semctl(SemId, 0, SETVAL, setval);
if (RetStat < 0) {
perror("SET SEMOP Failure: ");
exit(EXIT_FAILURE);
}
dbgprt("USLEEP/BEF\n");
usleep(1000 * 1000);
dbgprt("USLEEP/AFT\n");
}
return EXIT_SUCCESS;
}
// Program that waits for the semaphore set by the above program.
int
pgmb(void)
{
key_t SemKey = 0x12345;
int SemId;
pgmtail = "pgmb";
// NOTE/BUG: we must add IPC_CREAT if we start pgmb first
u_int Flag = 0;
Flag |= 0666;
#if 1
Flag |= IPC_CREAT;
#endif
SemId = semget(SemKey, 2, Flag);
if (SemId == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
DriverModule(SemId);
dbgprt("pgmb: complete\n");
return EXIT_SUCCESS;
}
char *
exmsg(int status)
{
static char buf[1000];
char *bp = buf;
bp += sprintf(bp,"status=[%8.8X ",status);
do {
if (WIFEXITED(status)) {
bp += sprintf(bp,"exited status=%d", WEXITSTATUS(status));
break;
}
if (WIFSIGNALED(status)) {
bp += sprintf(bp,"killed by signal %d", WTERMSIG(status));
break;
}
if (WIFSTOPPED(status)) {
bp += sprintf(bp,"stopped by signal %d", WSTOPSIG(status));
break;
}
bp += sprintf(bp,"continued");
} while (0);
bp += sprintf(bp,"]");
return buf;
}
void
testit(int newval)
{
int code;
pid_t pid;
opt_y = newval;
for (int phase = 1; phase <= 2; ++phase) {
// start the receiver
pid = fork();
if (pid != 0) {
dbgprt("testit: pid=%d\n",pid);
continue;
}
switch (phase) {
case 1:
code = pgma();
break;
case 2:
code = pgmb();
break;
default:
code = 99;
break;
}
exit(code);
}
while (1) {
int status;
pid = wait(&status);
if (pid <= 0)
break;
dbgprt("main: pid %d reaped -- %s\n",pid,exmsg(status));
}
}
int
main(int argc,char **argv)
{
//pid_t pid;
--argc;
++argv;
pgmtail = "main";
tvzero = tscget();
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'n':
opt_n = ! opt_n;
break;
}
}
opt_y = ! opt_n;
dbgprt("pgma: opt_y=%d\n",opt_y);
//testit(0);
testit(1);
return 0;
}
void
timer_set(int usec)
{
dbgprt("timer_set: SET usec=%d\n",usec);
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = usec;
/* ... and every 500 msec after that. */
// NOTE/BUG: timer must _not_ be rearmed -- can abort unrelated syscalls
timer.it_interval.tv_sec = 0;
#if 0
timer.it_interval.tv_usec = 500000;
#else
timer.it_interval.tv_usec = 0;
#endif
setitimer(itype, &timer, NULL);
}
void
timer_handler(int signum)
{
static int count = 0;
// NOTE/BUG: printf may _not_ be called from a signal handler
//dbgprt("timer expired %d times\n", ++count);
// Stop the timer
// NOTE/BUG: disabling now done elsewhere
if (count >= MAX_ITERATIONS) {
//dbgprt("Forcing Time Out Termination.....\n");
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
#if 0
setitimer(ITIMER_VIRTUAL, &timer, NULL);
#else
setitimer(ITIMER_REAL, &timer, NULL);
#endif
return;
}
}
int
DriverModule(int SemId)
{
struct sigaction sa;
sigset_t set;
int signo;
#if 0
itype = ITIMER_VIRTUAL;
signo = SIGVTALRM;
#else
itype = ITIMER_REAL;
signo = SIGALRM;
#endif
/* Install timer_handler as the signal handler for SIGVTALRM. */
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_SIGINFO;
sa.sa_handler = &timer_handler;
sigaction(signo, &sa, NULL);
sigemptyset(&set);
sigaddset(&set,signo);
sigprocmask(SIG_UNBLOCK,&set,NULL);
/* Configure the timer to expire after 250 msec... */
// Start virtual timer. It counts down whenever this process is executing.
#if 0
setitimer(itype, &timer, NULL);
#else
timer_set(250000);
#endif
dbgprt("Before calling wait for semaphore.......\n");
// Waiting for sempahore
if (! WaitForSemaphore(SemId, 0)) {
dbgprt("Unable to get sempahore.\n");
return 0;
}
dbgprt("After calling after calling wait for semaphoe module.........\n");
return 1;
}
int
WaitForSemaphore(int SemId, unsigned short SemNum)
{
struct sembuf SemBuf;
int RetStat;
unsigned int NoOfSemOps;
while (1) {
SemBuf.sem_num = SemNum;
SemBuf.sem_op = -1;
SemBuf.sem_flg = 0;
NoOfSemOps = 1;
dbgprt("WaitFor: SEMOP\n");
RetStat = semop(SemId, &SemBuf, NoOfSemOps);
if (RetStat >= 0) {
dbgprt("WaitFor: OKAY\n");
break;
}
if (errno == EINTR) {
dbgprt("WaitFor: EINTR\n");
// do stuff here ...
// rearm timer
timer_set(250000);
continue;
}
perror("Wait SEMOP Failure: ");
exit(1);
}
// NOTE/BUG: _must_ always disable timer to prevent other syscalls from being
// interrupted
#if 1
timer_set(0);
#endif
return 1;
}
这是重构程序的一些示例输出:
0.000000332 main pgma: opt_y=1
0.000182324 main testit: pid=849558
0.000267203 main testit: pid=849559
0.000830005 pgma pgma: SET phase=0
0.000847541 pgmb timer_set: SET usec=250000
0.000882037 pgma USLEEP/BEF
0.000891077 pgmb Before calling wait for semaphore.......
0.000895977 pgmb WaitFor: SEMOP
0.250932859 pgmb WaitFor: EINTR
0.250950128 pgmb timer_set: SET usec=250000
0.250956676 pgmb WaitFor: SEMOP
0.500996272 pgmb WaitFor: EINTR
0.501014687 pgmb timer_set: SET usec=250000
0.501021903 pgmb WaitFor: SEMOP
0.751066428 pgmb WaitFor: EINTR
0.751089504 pgmb timer_set: SET usec=250000
0.751097693 pgmb WaitFor: SEMOP
1.000970921 pgma USLEEP/AFT
1.000987303 pgma pgma: SET phase=1
1.001001916 pgma USLEEP/BEF
1.001046071 pgmb WaitFor: OKAY
1.001055982 pgmb timer_set: SET usec=0
1.001062505 pgmb After calling after calling wait for semaphoe module.........
1.001066632 pgmb pgmb: complete
1.001210687 main main: pid 849559 reaped -- status=[00000000 exited status=0]
2.001078396 pgma USLEEP/AFT
2.001269995 main main: pid 849558 reaped -- status=[00000000 exited status=0]
更新:
- Why re-arming the timer is required inside
WaitForSemaphore
?
通过注释掉信号处理程序中的 printf
调用 [由于信号安全问题我们必须这样做],作为副作用,count
的增量无效。
起初,我只是注释掉 printf
,没有意识到副作用“破坏”了处理程序的解除代码。过了一会儿,我意识到我做了什么并且想要效果(即让处理程序“什么都不做”)。
因此,整个信号处理程序 [有效地] 除了允许信号被拦截 [并导致挂起的系统调用接收 EINTR
错误之外什么都不做——这正是我们真正想要的]。 =56=]
我忽略了在评论中明确说明这一点[并用 #if 0
注释掉所有代码]。
例如,由于我最初的困惑,最好不要将active/necessary代码[count++
]放在调试代码中( printf
)。所以,printf(...,count++);
会更好:printf(...); count++;
这是一种体系结构选择,可以让基线代码 [non-handler 代码] 控制 arm/disarm。这是因为基线 不知道 是否知道处理程序是否做了任何事情,因此无论如何它都必须明确地解除武装 [以防止 timer/signal 在等待循环代码].
一般来说,[在内核中]有一个周期性的定时器中断肯定是有用的,并且通过类比,作为一个周期性信号有点有用,这对于这里只是唤醒一个 [卡住]系统调用。
- Why
usleep
is required inside pgma
module?
这只是为了强制 pgmb
查看 两个 超时调用 semop
的条件和 testing/validation 的正常调用目的。在 normal/production 代码中,pgma
不需要睡眠。
开始时,pgma
向信号量发送一个 0
值。然后它会休眠 1 秒(1000 毫秒)。
当 pgmb
启动时,它会看到此 0
值 [并在 ~250 毫秒后超时]。 pgmb
将循环大约 4 次,直到 pgma
醒来并发布一个 1
值 [这将正常完成 pgmb
的 semop
]。 =56=]
所以,现在,我们已经 pgmb
体验 failed/timeout 案例 和 normal/successful个案例。
这就是一组[好的]单元测试应该做的:测试目标代码[pgmb
中semop
的等待循环]对于所有可能的modes/states.
在这段代码中,我们通过查看调试输出看到了这一点。对于一组真正的单元测试,我们需要额外的代码以编程方式对此进行检查。
我有一种情况需要在阻塞操作之前启动到期计时器。如果控制在预定的时间间隔内没有从阻塞操作中出来,我需要继续前进并恢复其他活动。该程序不应在计时器到期时终止。相反,我必须执行阻塞操作之后的代码。在下面的代码片段中,当计时器到期时,我需要去 B 点而不是退出。工作环境为ubuntu18.04,代码为c。我探索了 setitimer
函数调用以及与信号处理程序相关联的函数调用。但是控制并没有跳过阻塞操作并继续执行后续代码。
Program to create a semaphore and setting the semaphore for use by another program.
int main(int argc,char *argv[])
{
key_t SemKey = 0x12345 ;
int SemId = -1;
int NumOfSems = 2 ;
int Flag ;
int RetStat ;
char choice = 'n' ;
struct
{
int val;
struct semid_ds *buf;
ushort array[2] ;
} Arg ;
Flag = IPC_CREAT | IPC_EXCL | 0666 ;
SemId = semget(SemKey,NumOfSems,Flag ) ;
if ( SemId == -1 )
{
perror("semget");
exit(EXIT_FAILURE);
}
Arg.array[0] = 0 ;
Arg.array[1] = 0 ;
RetStat = semctl(SemId, 0, SETALL, Arg.array);
if (RetStat == -1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
printf("\nPress y or Y to continue ahead.\n");
scanf("%c",&choice);
if ( ( choice != 'y') && ( choice != 'Y'))
exit(EXIT_FAILURE);
if ( ( choice != 'y') && ( choice != 'Y'))
exit(EXIT_FAILURE);
RetStat = semctl( SemId,0, SETVAL, 1 );
if( RetStat < 0 )
{
perror( "SET SEMOP Failure: " );
exit(EXIT_FAILURE);
}
return EXIT_SUCCESS ;
}
Program that waits for the semaphore set by the above program.
int DriverModule(int );
int main(int argc,char *argv[])
{
key_t SemKey = 0x12345 ;
int SemId ;
u_int Flag;
Flag = 0666 ;
SemId = semget( SemKey, 0, Flag );
if ( SemId == -1 )
{
perror("semget");
exit(EXIT_FAILURE);
}
DriverModule(SemId) ;
return EXIT_SUCCESS ;
}
#define MAX_ITERATIONS 100
struct itimerval timer;
int WaitForSemaphore(int ,unsigned short ) ;
void timer_handler (int signum)
{
static int count = 0;
printf ("timer expired %d times\n", ++count);
if ( count >= MAX_ITERATIONS ) // Stop the timer
{
printf("\n\nForcing Time Out Termination.....\n\n");
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
setitimer (ITIMER_VIRTUAL, &timer, NULL);
return ;
}
}
int DriverModule (int SemId)
{
struct sigaction sa;
/* Install timer_handler as the signal handler for SIGVTALRM. */
memset (&sa, 0, sizeof (sa));
sa.sa_flags = SA_SIGINFO;
sa.sa_handler = &timer_handler;
sigaction (SIGVTALRM, &sa, NULL);
/* Configure the timer to expire after 250 msec... */
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 250000;
/* ... and every 500 msec after that. */
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 500000;
/* Start a virtual timer. It counts down whenever this process is
executing. */
setitimer (ITIMER_VIRTUAL, &timer, NULL);
printf("\nBefore calling wait for semaphore.......\n");
// Waiting for sempahore
if( !WaitForSemaphore( SemId, 0) )
{
printf("\nUnable to get sempahore.\n");
return 0 ;
}
printf("\nAfter calling after calling wait for semaphoe module.........\n");
return 1 ;
}
int WaitForSemaphore(int SemId,unsigned short SemNum )
{
struct sembuf SemBuf;
int RetStat;
unsigned int NoOfSemOps;
SemBuf.sem_num = SemNum;
SemBuf.sem_op = -1;
SemBuf.sem_flg = 0;
NoOfSemOps = 1;
RetStat = semop( SemId, &SemBuf, NoOfSemOps );
if( RetStat < 0 )
{
if( errno == EINTR )
{
WaitForSemaphore( SemId, SemNum );
}
else
{
perror( "Wait SEMOP Failure: " );
return 0 ;
}
}
return 1 ;
}
好的,主要问题是使用 ITIMER_VIRTUAL
。当进程为 运行 时,此 [类型] 计数器 仅 递减。如果进程正在执行系统调用,则它 不是 运行。
因此,我们必须改用 ITIMER_REAL
。而且,如果我们这样做,它会生成一个 SIGALRM
信号 [并且 而不是 一个 SIGVTALRM
信号。
进行这些更改后,出现了另一个问题。
定时器必须被禁用在它正在保护的系统调用之后(例如semop
)。否则,unexpired 计时器(即 semop
确实 not 超时)可能会中断 another/subsequent/unrelated 系统调用(例如 read
、write
、等等)。
因此,在下面的代码中,我们需要(例如):
timer_set(250000);
semop(...);
timer_set(0);
另外,请注意,可以从信号处理程序[安全]执行有限 系统调用[and/or 库函数调用]。值得注意的是,printf
不能 不能 用于信号处理程序。
而且,即使进行“信号安全”系统调用也需要信号处理程序 [在进入时] 保存并在 [退出时] 恢复 errno
的原始值。
否则,基线代码(例如 [中断] semop
之后)将看不到正确的 errno
值(例如 EINTR
),而是 errno
值对于信号处理程序选择执行的任何系统调用。
我不得不对代码进行相当多的重构才能制作出合理的测试程序。
我将这两个程序组合成一个测试程序,以便为正常情况和超时情况生成适当的单元测试,由于计时问题和竞争条件,这对于它们作为单独的程序来说很难。
我还增强了调试打印输出。
无论如何,这是代码:
// create a semaphore and setting the semaphore for use by another program.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/time.h>
#include <sys/wait.h>
int opt_n;
int opt_y;
#define MAX_ITERATIONS 100
int itype;
struct itimerval timer;
const char *pgmtail;
int WaitForSemaphore(int, unsigned short);
int DriverModule(int);
typedef long long tsc_t;
tsc_t tvzero;
tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
clock_gettime(CLOCK_MONOTONIC,&ts);
tsc = ts.tv_sec;
tsc *= 1000000000;
tsc += ts.tv_nsec;
tsc -= tvzero;
return tsc;
}
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= 1e9;
return sec;
}
void
dbgprt(const char *fmt,...)
{
va_list ap;
char buf[1000];
char *bp = buf;
bp += sprintf(bp,"%.9f %s ",tscsec(tscget()),pgmtail);
va_start(ap,fmt);
bp += vsprintf(bp,fmt,ap);
va_end(ap);
fputs(buf,stdout);
fflush(stdout);
}
int
pgma(void)
{
key_t SemKey = 0x12345;
pgmtail = "pgma";
int SemId = -1;
int NumOfSems = 2;
int Flag = 0;
int RetStat;
#if 0
char choice = 'n';
#endif
#if 0
struct {
int val;
struct semid_ds *buf;
ushort array[2];
} Arg;
#endif
Flag |= IPC_CREAT;
//Flag |= IPC_EXCL;
Flag |= 0666;
SemId = semget(SemKey, NumOfSems, Flag);
if (SemId == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
#if 0
Arg.array[0] = 0;
Arg.array[1] = 0;
RetStat = semctl(SemId, 0, SETALL, Arg.array);
if (RetStat == -1) {
perror("semctl");
exit(EXIT_FAILURE);
}
#endif
for (int phase = 0; phase <= opt_y; ++phase) {
int setval = phase;
dbgprt("pgma: SET phase=%d\n",phase);
RetStat = semctl(SemId, 0, SETVAL, setval);
if (RetStat < 0) {
perror("SET SEMOP Failure: ");
exit(EXIT_FAILURE);
}
dbgprt("USLEEP/BEF\n");
usleep(1000 * 1000);
dbgprt("USLEEP/AFT\n");
}
return EXIT_SUCCESS;
}
// Program that waits for the semaphore set by the above program.
int
pgmb(void)
{
key_t SemKey = 0x12345;
int SemId;
pgmtail = "pgmb";
// NOTE/BUG: we must add IPC_CREAT if we start pgmb first
u_int Flag = 0;
Flag |= 0666;
#if 1
Flag |= IPC_CREAT;
#endif
SemId = semget(SemKey, 2, Flag);
if (SemId == -1) {
perror("semget");
exit(EXIT_FAILURE);
}
DriverModule(SemId);
dbgprt("pgmb: complete\n");
return EXIT_SUCCESS;
}
char *
exmsg(int status)
{
static char buf[1000];
char *bp = buf;
bp += sprintf(bp,"status=[%8.8X ",status);
do {
if (WIFEXITED(status)) {
bp += sprintf(bp,"exited status=%d", WEXITSTATUS(status));
break;
}
if (WIFSIGNALED(status)) {
bp += sprintf(bp,"killed by signal %d", WTERMSIG(status));
break;
}
if (WIFSTOPPED(status)) {
bp += sprintf(bp,"stopped by signal %d", WSTOPSIG(status));
break;
}
bp += sprintf(bp,"continued");
} while (0);
bp += sprintf(bp,"]");
return buf;
}
void
testit(int newval)
{
int code;
pid_t pid;
opt_y = newval;
for (int phase = 1; phase <= 2; ++phase) {
// start the receiver
pid = fork();
if (pid != 0) {
dbgprt("testit: pid=%d\n",pid);
continue;
}
switch (phase) {
case 1:
code = pgma();
break;
case 2:
code = pgmb();
break;
default:
code = 99;
break;
}
exit(code);
}
while (1) {
int status;
pid = wait(&status);
if (pid <= 0)
break;
dbgprt("main: pid %d reaped -- %s\n",pid,exmsg(status));
}
}
int
main(int argc,char **argv)
{
//pid_t pid;
--argc;
++argv;
pgmtail = "main";
tvzero = tscget();
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'n':
opt_n = ! opt_n;
break;
}
}
opt_y = ! opt_n;
dbgprt("pgma: opt_y=%d\n",opt_y);
//testit(0);
testit(1);
return 0;
}
void
timer_set(int usec)
{
dbgprt("timer_set: SET usec=%d\n",usec);
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = usec;
/* ... and every 500 msec after that. */
// NOTE/BUG: timer must _not_ be rearmed -- can abort unrelated syscalls
timer.it_interval.tv_sec = 0;
#if 0
timer.it_interval.tv_usec = 500000;
#else
timer.it_interval.tv_usec = 0;
#endif
setitimer(itype, &timer, NULL);
}
void
timer_handler(int signum)
{
static int count = 0;
// NOTE/BUG: printf may _not_ be called from a signal handler
//dbgprt("timer expired %d times\n", ++count);
// Stop the timer
// NOTE/BUG: disabling now done elsewhere
if (count >= MAX_ITERATIONS) {
//dbgprt("Forcing Time Out Termination.....\n");
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
#if 0
setitimer(ITIMER_VIRTUAL, &timer, NULL);
#else
setitimer(ITIMER_REAL, &timer, NULL);
#endif
return;
}
}
int
DriverModule(int SemId)
{
struct sigaction sa;
sigset_t set;
int signo;
#if 0
itype = ITIMER_VIRTUAL;
signo = SIGVTALRM;
#else
itype = ITIMER_REAL;
signo = SIGALRM;
#endif
/* Install timer_handler as the signal handler for SIGVTALRM. */
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_SIGINFO;
sa.sa_handler = &timer_handler;
sigaction(signo, &sa, NULL);
sigemptyset(&set);
sigaddset(&set,signo);
sigprocmask(SIG_UNBLOCK,&set,NULL);
/* Configure the timer to expire after 250 msec... */
// Start virtual timer. It counts down whenever this process is executing.
#if 0
setitimer(itype, &timer, NULL);
#else
timer_set(250000);
#endif
dbgprt("Before calling wait for semaphore.......\n");
// Waiting for sempahore
if (! WaitForSemaphore(SemId, 0)) {
dbgprt("Unable to get sempahore.\n");
return 0;
}
dbgprt("After calling after calling wait for semaphoe module.........\n");
return 1;
}
int
WaitForSemaphore(int SemId, unsigned short SemNum)
{
struct sembuf SemBuf;
int RetStat;
unsigned int NoOfSemOps;
while (1) {
SemBuf.sem_num = SemNum;
SemBuf.sem_op = -1;
SemBuf.sem_flg = 0;
NoOfSemOps = 1;
dbgprt("WaitFor: SEMOP\n");
RetStat = semop(SemId, &SemBuf, NoOfSemOps);
if (RetStat >= 0) {
dbgprt("WaitFor: OKAY\n");
break;
}
if (errno == EINTR) {
dbgprt("WaitFor: EINTR\n");
// do stuff here ...
// rearm timer
timer_set(250000);
continue;
}
perror("Wait SEMOP Failure: ");
exit(1);
}
// NOTE/BUG: _must_ always disable timer to prevent other syscalls from being
// interrupted
#if 1
timer_set(0);
#endif
return 1;
}
这是重构程序的一些示例输出:
0.000000332 main pgma: opt_y=1
0.000182324 main testit: pid=849558
0.000267203 main testit: pid=849559
0.000830005 pgma pgma: SET phase=0
0.000847541 pgmb timer_set: SET usec=250000
0.000882037 pgma USLEEP/BEF
0.000891077 pgmb Before calling wait for semaphore.......
0.000895977 pgmb WaitFor: SEMOP
0.250932859 pgmb WaitFor: EINTR
0.250950128 pgmb timer_set: SET usec=250000
0.250956676 pgmb WaitFor: SEMOP
0.500996272 pgmb WaitFor: EINTR
0.501014687 pgmb timer_set: SET usec=250000
0.501021903 pgmb WaitFor: SEMOP
0.751066428 pgmb WaitFor: EINTR
0.751089504 pgmb timer_set: SET usec=250000
0.751097693 pgmb WaitFor: SEMOP
1.000970921 pgma USLEEP/AFT
1.000987303 pgma pgma: SET phase=1
1.001001916 pgma USLEEP/BEF
1.001046071 pgmb WaitFor: OKAY
1.001055982 pgmb timer_set: SET usec=0
1.001062505 pgmb After calling after calling wait for semaphoe module.........
1.001066632 pgmb pgmb: complete
1.001210687 main main: pid 849559 reaped -- status=[00000000 exited status=0]
2.001078396 pgma USLEEP/AFT
2.001269995 main main: pid 849558 reaped -- status=[00000000 exited status=0]
更新:
- Why re-arming the timer is required inside
WaitForSemaphore
?
通过注释掉信号处理程序中的 printf
调用 [由于信号安全问题我们必须这样做],作为副作用,count
的增量无效。
起初,我只是注释掉 printf
,没有意识到副作用“破坏”了处理程序的解除代码。过了一会儿,我意识到我做了什么并且想要效果(即让处理程序“什么都不做”)。
因此,整个信号处理程序 [有效地] 除了允许信号被拦截 [并导致挂起的系统调用接收 EINTR
错误之外什么都不做——这正是我们真正想要的]。 =56=]
我忽略了在评论中明确说明这一点[并用 #if 0
注释掉所有代码]。
例如,由于我最初的困惑,最好不要将active/necessary代码[count++
]放在调试代码中( printf
)。所以,printf(...,count++);
会更好:printf(...); count++;
这是一种体系结构选择,可以让基线代码 [non-handler 代码] 控制 arm/disarm。这是因为基线 不知道 是否知道处理程序是否做了任何事情,因此无论如何它都必须明确地解除武装 [以防止 timer/signal 在等待循环代码].
一般来说,[在内核中]有一个周期性的定时器中断肯定是有用的,并且通过类比,作为一个周期性信号有点有用,这对于这里只是唤醒一个 [卡住]系统调用。
- Why
usleep
is required insidepgma
module?
这只是为了强制 pgmb
查看 两个 超时调用 semop
的条件和 testing/validation 的正常调用目的。在 normal/production 代码中,pgma
不需要睡眠。
开始时,pgma
向信号量发送一个 0
值。然后它会休眠 1 秒(1000 毫秒)。
当 pgmb
启动时,它会看到此 0
值 [并在 ~250 毫秒后超时]。 pgmb
将循环大约 4 次,直到 pgma
醒来并发布一个 1
值 [这将正常完成 pgmb
的 semop
]。 =56=]
所以,现在,我们已经 pgmb
体验 failed/timeout 案例 和 normal/successful个案例。
这就是一组[好的]单元测试应该做的:测试目标代码[pgmb
中semop
的等待循环]对于所有可能的modes/states.
在这段代码中,我们通过查看调试输出看到了这一点。对于一组真正的单元测试,我们需要额外的代码以编程方式对此进行检查。