sigaction() 与 signal() 有何不同?
How sigaction() differs from signal()?
prog2
不会在 CTRL+D
上退出。为什么 prog1
在 CTRL+D
上退出呢?更奇怪的是,信号处理程序不执行任何操作,尽管它以某种方式影响最终结果...
以下两个程序的区别仅在于prog1.c
中使用了sigaction()
,而在prog2.c
中使用了signal()
:
@@ -39,10 +39,7 @@
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
- struct sigaction sa;
- sa.sa_handler = child_handler;
- sa.sa_flags = 0;
- sigaction(SIGCHLD, &sa, NULL);
+ signal(SIGCHLD, child_handler);
struct termios tty;
tcgetattr(fd, &tty);
这些程序中的每一个都简单地打开一个环回tty并将自己分成两个进程,一个从tty读取响应,另一个将数据写入tty设备。然后从loopback tty虚拟设备接收到的数据输出到控制终端。
使用 -lutil
选项编译 prog1
和 prog2
。启动每个程序并键入 hello<CTRL+D>
。这导致以下输出:
$ ./prog1
hello$
$ ./prog2
hello
顺便说一句,应该在 sigaction()
中设置哪些标志以复制 signal()
的行为?
以下是节目:
prog1.c
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
int fd;
void child_handler(int s)
{
(void) s;
}
int main(void)
{
char c;
/* open loopback tty device */
pid_t lpid;
lpid = forkpty(&fd, NULL, NULL, NULL);
if (lpid == -1) {
exit(1);
}
if (lpid == 0) {
char *args[] = { "cat", NULL };
execv("/bin/cat", args);
}
/* create parallel process */
pid_t cpid;
cpid = fork();
if (cpid == -1) {
close(fd);
exit(1);
}
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
struct sigaction sa;
sa.sa_handler = child_handler;
sa.sa_flags = 0;
sigaction(SIGCHLD, &sa, NULL);
struct termios tty;
tcgetattr(fd, &tty);
cfmakeraw(&tty);
tcsetattr(fd, TCSANOW, &tty);
while (read(fd, &c, 1) != -1)
write(STDOUT_FILENO, &c, 1);
}
/* write to loopback tty */
if (cpid == 0) {
struct termios stdtio_restore;
struct termios stdtio;
tcgetattr(STDIN_FILENO, &stdtio_restore);
tcgetattr(STDIN_FILENO, &stdtio);
cfmakeraw(&stdtio);
tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);
while (1) {
read(STDIN_FILENO, &c, 1);
if (c == 0x04) break;
write(fd, &c, 1);
}
tcsetattr(0, TCSANOW, &stdtio_restore);
close(fd);
exit(0);
}
return 0;
}
prog2.c
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
int fd;
void child_handler(int s)
{
(void) s;
}
int main(void)
{
char c;
/* open loopback tty device */
pid_t lpid;
lpid = forkpty(&fd, NULL, NULL, NULL);
if (lpid == -1) {
exit(1);
}
if (lpid == 0) {
char *args[] = { "cat", NULL };
execv("/bin/cat", args);
}
/* create parallel process */
pid_t cpid;
cpid = fork();
if (cpid == -1) {
close(fd);
exit(1);
}
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
signal(SIGCHLD, child_handler);
struct termios tty;
tcgetattr(fd, &tty);
cfmakeraw(&tty);
tcsetattr(fd, TCSANOW, &tty);
while (read(fd, &c, 1) != -1)
write(STDOUT_FILENO, &c, 1);
}
/* write to loopback tty */
if (cpid == 0) {
struct termios stdtio_restore;
struct termios stdtio;
tcgetattr(STDIN_FILENO, &stdtio_restore);
tcgetattr(STDIN_FILENO, &stdtio);
cfmakeraw(&stdtio);
tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);
while (1) {
read(STDIN_FILENO, &c, 1);
if (c == 0x04) break;
write(fd, &c, 1);
}
tcsetattr(0, TCSANOW, &stdtio_restore);
close(fd);
exit(0);
}
return 0;
}
The difference in behavior is likely because signal
behaves as if the SA_RESTART
flag was set on sigaction
. From the signal(2) manual page:
The BSD semantics are equivalent to calling sigaction(2) with the
following flags:
sa.sa_flags = SA_RESTART;
The situation on Linux is as follows:
The kernel's signal() system call provides System V semantics.
By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls
sigaction(2) using flags that supply BSD semantics...
When the SA_RESTART
flag is used some system calls are automatically restarted. When it is not used the call will return an error with errno set to EINTR
.
Thus in the "read from loopback" process in prog1
the following happens:
- Your process is blocked in
read
- It receives SIGCHLD and 运行s the handler.
- The
read
system call it was blocked in returns -1
- The loop exits based on the
while
condition and the process exits.
In prog2
, the SA_RESTART
behavior means that after the signal handler is 运行 in (2), the read
call is restarted.
To make prog1
behave like prog2
, set SA_RESTART
:
sa.sa_flags = SA_RESTART;
See the "Interruption of system calls and library functions by signal handlers" section of the signal(7) manual page for more detail on SA_RESTART
's behavior.
prog2
不会在 CTRL+D
上退出。为什么 prog1
在 CTRL+D
上退出呢?更奇怪的是,信号处理程序不执行任何操作,尽管它以某种方式影响最终结果...
以下两个程序的区别仅在于prog1.c
中使用了sigaction()
,而在prog2.c
中使用了signal()
:
@@ -39,10 +39,7 @@
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
- struct sigaction sa;
- sa.sa_handler = child_handler;
- sa.sa_flags = 0;
- sigaction(SIGCHLD, &sa, NULL);
+ signal(SIGCHLD, child_handler);
struct termios tty;
tcgetattr(fd, &tty);
这些程序中的每一个都简单地打开一个环回tty并将自己分成两个进程,一个从tty读取响应,另一个将数据写入tty设备。然后从loopback tty虚拟设备接收到的数据输出到控制终端。
使用 -lutil
选项编译 prog1
和 prog2
。启动每个程序并键入 hello<CTRL+D>
。这导致以下输出:
$ ./prog1
hello$
$ ./prog2
hello
顺便说一句,应该在 sigaction()
中设置哪些标志以复制 signal()
的行为?
以下是节目:
prog1.c
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
int fd;
void child_handler(int s)
{
(void) s;
}
int main(void)
{
char c;
/* open loopback tty device */
pid_t lpid;
lpid = forkpty(&fd, NULL, NULL, NULL);
if (lpid == -1) {
exit(1);
}
if (lpid == 0) {
char *args[] = { "cat", NULL };
execv("/bin/cat", args);
}
/* create parallel process */
pid_t cpid;
cpid = fork();
if (cpid == -1) {
close(fd);
exit(1);
}
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
struct sigaction sa;
sa.sa_handler = child_handler;
sa.sa_flags = 0;
sigaction(SIGCHLD, &sa, NULL);
struct termios tty;
tcgetattr(fd, &tty);
cfmakeraw(&tty);
tcsetattr(fd, TCSANOW, &tty);
while (read(fd, &c, 1) != -1)
write(STDOUT_FILENO, &c, 1);
}
/* write to loopback tty */
if (cpid == 0) {
struct termios stdtio_restore;
struct termios stdtio;
tcgetattr(STDIN_FILENO, &stdtio_restore);
tcgetattr(STDIN_FILENO, &stdtio);
cfmakeraw(&stdtio);
tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);
while (1) {
read(STDIN_FILENO, &c, 1);
if (c == 0x04) break;
write(fd, &c, 1);
}
tcsetattr(0, TCSANOW, &stdtio_restore);
close(fd);
exit(0);
}
return 0;
}
prog2.c
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
int fd;
void child_handler(int s)
{
(void) s;
}
int main(void)
{
char c;
/* open loopback tty device */
pid_t lpid;
lpid = forkpty(&fd, NULL, NULL, NULL);
if (lpid == -1) {
exit(1);
}
if (lpid == 0) {
char *args[] = { "cat", NULL };
execv("/bin/cat", args);
}
/* create parallel process */
pid_t cpid;
cpid = fork();
if (cpid == -1) {
close(fd);
exit(1);
}
/* read from loopback tty */
if (cpid > 0) {
/* this is the strange part */
signal(SIGCHLD, child_handler);
struct termios tty;
tcgetattr(fd, &tty);
cfmakeraw(&tty);
tcsetattr(fd, TCSANOW, &tty);
while (read(fd, &c, 1) != -1)
write(STDOUT_FILENO, &c, 1);
}
/* write to loopback tty */
if (cpid == 0) {
struct termios stdtio_restore;
struct termios stdtio;
tcgetattr(STDIN_FILENO, &stdtio_restore);
tcgetattr(STDIN_FILENO, &stdtio);
cfmakeraw(&stdtio);
tcsetattr(STDIN_FILENO, TCSANOW, &stdtio);
while (1) {
read(STDIN_FILENO, &c, 1);
if (c == 0x04) break;
write(fd, &c, 1);
}
tcsetattr(0, TCSANOW, &stdtio_restore);
close(fd);
exit(0);
}
return 0;
}
The difference in behavior is likely because signal
behaves as if the SA_RESTART
flag was set on sigaction
. From the signal(2) manual page:
The BSD semantics are equivalent to calling sigaction(2) with the following flags:
sa.sa_flags = SA_RESTART;
The situation on Linux is as follows:
The kernel's signal() system call provides System V semantics.
By default, in glibc 2 and later, the signal() wrapper function does not invoke the kernel system call. Instead, it calls sigaction(2) using flags that supply BSD semantics...
When the SA_RESTART
flag is used some system calls are automatically restarted. When it is not used the call will return an error with errno set to EINTR
.
Thus in the "read from loopback" process in prog1
the following happens:
- Your process is blocked in
read
- It receives SIGCHLD and 运行s the handler.
- The
read
system call it was blocked in returns -1 - The loop exits based on the
while
condition and the process exits.
In prog2
, the SA_RESTART
behavior means that after the signal handler is 运行 in (2), the read
call is restarted.
To make prog1
behave like prog2
, set SA_RESTART
:
sa.sa_flags = SA_RESTART;
See the "Interruption of system calls and library functions by signal handlers" section of the signal(7) manual page for more detail on SA_RESTART
's behavior.