最好使用sigaction函數替代signal函數

APUE 習題15.15,一個XSI共享存儲的測試程序:

#include <fcntl.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdbool.h>
#include <signal.h>
#include <stddef.h>

static volatile sig_atomic_t sigflag;
static sigset_t newmask, oldmask, zeromask;

static void sig_usr(int signo)
{
	sigflag = 1;
}

bool TELL_WAIT()
{
    if(signal(SIGUSR1, sig_usr) != 0) return false;
    if(sigemptyset(&zeromask) != 0) return false;
    if(sigemptyset(&newmask) != 0) return false;
    if(sigaddset(&newmask, SIGUSR1) != 0) return false;
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) != 0) return false;
    return true;
}
void TELL(pid_t pid)
{
    kill(pid, SIGUSR1);
}
void WAIT()
{
    while(sigflag == 0) sigsuspend(&zeromask);
    sigflag = 0;
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
}


static int update(int *ptr)
{
	return ((*ptr)++);
}

int main()
{
	int shmid = shmget(IPC_PRIVATE, sizeof(int), 0600);
	assert(shmid != -1);

	TELL_WAIT();

	pid_t pid = fork();
	if(pid < 0) return 3;
	else if(pid > 0)
	{
		void *area = shmat(shmid, 0, 0);
		assert(area != (void*)-1);
		printf("parent shm addr: %p\n", area);
		for(int i=0; i<100; i+=2)
		{
			printf("parent %d\n", update((int*)area));
			TELL(pid);
			WAIT();
		}
		shmdt(area);
	}
	else
	{
		void *area = shmat(shmid, 0, 0);
		assert(area != (void*)-1);
		printf("child shm addr: %p\n", area);
		for(int i=1; i<101; i+=2)
		{
			WAIT();
			printf("child %d\n", update((int*)area));
			TELL(getppid());
		}
		shmdt(area);
		shmctl(shmid, IPC_RMID, 0);
	}
	return 0;
}

gcc -std=c99 -D_XOPEN_SOURCE test.c
運行結果不對:
parent shm addr: 0x7f9a4476e000
parent 0
child shm addr: 0x7f9a4476e000
child 1
parent 2
然後查看子進程處於<defunct>狀態,子進程退出了,父進程還在WAIT。
修改父進程,調用wait()獲取子進程狀態是10,正好是信號SIGUSR1的值,懷疑是子進程收到SIGUSR1就退出了,查了SIGUSR1的默認動作是終止程序。在WAIT()中sigsuspend返回之後重新設置信號處理程序,輸出變正常了。

結合man和帖子signal()註冊的信號處理函數裏面,不需要再次註冊信號處理函數嗎?
The kernel’s signal() system call provides System V semantics. System V會在處理信號之後將信號處理程序重置爲SIG_DFL。如果編譯時加了額外的選項例如-std=c99,那麼用內核的signal()系統調用。默認情況下使用sigaction函數的方式。

嘗試去掉-std=c99並調整代碼並沒有作用,可能是還帶了-D_XOPEN_SOURCE;以下兩種方式是可以的:
g++ test.c
gcc -std=gnu99 test.c

APUE 10.14節對sigaction函數明確指出:一旦對給定的信號設置了一個動作,那麼在調用sigaction顯式地改變它之前,該設置一直有效。所以將

if(signal(SIGUSR1, sig_usr) != 0) return false;

替換爲

struct sigaction act;
act.sa_handler = sig_usr;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGUSR1, &act, NULL) != 0) return false;

也可以解決問題。

結論:
最好使用sigaction函數替代signal函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章