你的代码有一些错误:
-
避免在信号处理程序中调用printf( ) 函数。 SIGNAL(7) 手册提供了一个授权函数列表,调用它们在信号处理程序中是安全的。阅读:
Async-signal-safe functions
信号处理函数必须非常小心,因为处理
其他地方可能会在执行中的某个任意点被中断
的程序。 POSIX 有“安全功能”的概念。 如果一个
信号中断不安全函数的执行,以及处理程序
调用一个不安全的函数,那么程序的行为是
不明确的。
使用main()的返回类型int;阅读"What should main() return in C?"
x 应该是pid_t。 (Process Identification)。
现在让我们假设您的程序编译并运行(在处理程序执行时没有被任何其他信号中断):
我只是缩进你的代码并在 main 之前移动 f() 函数定义,因为缺少函数声明,还添加了一些你应该阅读的 cmets:
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
void f( )
{
printf ("signal received\n");
exit (0);//if child receives signal then child exits from here
} // ******* mostly happens
int main ( )
{ int x;
signal (SIGUSR1, f);// First: handler registered
x = fork ( ); // Second: Child created, now child will execute in
if (x == -1) // parallel with parent
exit (1);
if (x != 0) {// parent
kill(x, SIGUSR1) ; // Third: sends signal to child
sleep(2); // Forth: parent sleep
}
return 0; // parent always exits
// Child can exits from here ****
// if signal sent from parent delayed
}
在main() 函数中,您为SIGUSR1 信号注册f() 函数,然后调用fork() 来创建一个新进程。在运行时,fork() 函数返回一个子进程开始与父进程并行执行。
正如我可以看到您的代码,我认为您了解子进程是父进程的副本,除了变量的值可能与fork() 返回的点不同,因此x 在子进程和父进程中是不同的。我们可以使用 fork 的返回值来判断程序是在父进程中运行还是在子进程中运行。但请注意,接收信号SIGUSR1 的不是父进程,而是子进程。对于任何进程,自身进程 ID 的值始终为 0。您检查返回值x = fork(),它是新创建的子进程的pid,x 的子进程值是0,父进程x != 0。因此,信号从父进程发送到子进程。
你的cmets:
我认为当父进程收到SIGUSR1 信号时,上面的程序要求系统启动f( ) 函数(显示"signal received")。
我的印象是您不认为两个进程同时执行,并且“在fork() 创建子进程后不久,子进程开始执行并立即终止,然后父进程可以发送一个给子进程的信号(或子进程可以接收信号)”。在这种情况下,函数f() 将永远不会有机会执行,并且信号处理程序中的 printf 永远不会打印。
但是我上面描述的可能性非常低,因为 fork 需要时间来创建一个新进程。即使你一次又一次地执行代码,大多数时候从父进程发送的信号都会执行信号处理程序。
编写此代码的正确方法是什么?
代码是 x.c:正确的方法是设置一个标志,指示信号处理程序已执行,然后根据我在答案中描述的信号处理程序之外的标志值调用 printf 函数:@ 987654325@ 其背后的原因由Jonathan Leffler 在他的answer 中解释。
#define _POSIX_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
volatile sig_atomic_t flag = 0; //initially flag == 0
void f(){
flag = 1; // flag is one if handler executes
}
int main(void){
pid_t x;
(void) signal (SIGUSR1, f);
x = fork();
if(x == -1)
exit(EXIT_FAILURE);
if (x != 0){// parent
kill(x, SIGUSR1);
sleep(1);
}
if(flag)//print only if signal caught and flag == 1
printf("%s signal received \n", x == 0 ? "Child:" : "Parent:");
return EXIT_SUCCESS;
}
现在编译并执行:
@:~$ gcc -Wall -pedantic -std=c99 x.c -o x
@:~$ ./x
Child: signal received
@:~$
注意子进程会打印,因为父进程向子进程发送信号(但父进程不会打印,因为父进程中没有信号捕获)。因此,上述代码的行为仍然与您在代码中的行为相似。下面我又添加了一个示例,在该示例中我试图证明“进程的并发执行在不同的执行实例中会产生不同的结果”(读取 cmets)。
// include header files...
volatile sig_atomic_t flag = 0;
void f(){
flag = 1;
}
int main(void){
pid_t x;
(void) signal (SIGUSR1, f);
(void) signal (SIGUSR2, f); // added one more signal
x= fork ( );
if(x == -1)
exit(EXIT_FAILURE);
if (x != 0){// parent
kill(x, SIGUSR1);
while(!flag); // in loop until flag == 0
}
if (x == 0){//child
kill(getppid(), SIGUSR2); // send signal to parent
while(!flag); // in loop until flag == 0
}// ^^^^ loop terminates just after signal-handler sets `flag`
if(flag)
printf("%s signal received \n", x == 0 ? "Child:" : "Parent:");
return EXIT_SUCCESS;
}
在上面的代码中,在父进程和子进程中都注册了两个信号。在信号设置标志之前,父进程不会休眠,而是在一段时间循环中忙碌。同样,子进程有一个循环,该循环在信号处理程序中标志变为 1 时中断。现在编译这段代码并重复运行。我经常在我的系统中尝试得到以下输出。
@:~$ gcc -Wall -pedantic -std=c99 x.c -o x
@:~$ ./x
Child: signal received
Parent: signal received
@:~$ ./x
Child: signal received
Parent: signal received
@:~$ ./x
Child: signal received
Parent: signal received
@:~$ ./x
Parent: signal received // <------
@:~$ Child: signal received
./x
Child: signal received
Parent: signal received
@:~$ ./x
Parent: signal received // <------
@:~$ Child: signal received
@:~$
注意输出,一种情况是:“直到子进程创建父进程发送信号并进入while循环,当子进程有机会执行时(取决于CPU调度),它会在父进程之前向父进程发送信号有机会执行孩子接收信号并打印消息”。但有时也会发生在 child printf print 之前;父母接收并打印消息(我用箭头标记)。
在最后一个示例中,我试图展示子进程与父进程并行执行,如果您不应用并发控制机制,输出可能会有所不同。
一些学习信号的好资源 (1) GNU C 库:Signal Handling
(2) CERT C 编码标准11. Signals (SIG)。