Signal Programming

การส่งสัญญาณ (signal) ถือว่าเป็นการส่งข้อมูลขั้นพื้นฐานระหว่างโปรเซสภายใต้ระบบปฏิบัติการลีนุกซ์ โดยโปรเซสหนึ่งจะส่งสัญญาณไปยังอีกโปรเซสหนึ่ง เมื่ออีกโปรเซสที่ได้รับสัญญาณที่ถูกแทรกเข้ามา (interrupt) มันก็จะดำเนินการคำสั่งที่ถูกตั้งเอาไว้ทันที ซึ่งเมื่อเริ่มแรกของการนำสัญญาณมาใช้นั้น จะเป็นเพียงรูปแบบสัญญาณที่ไม่ได้แยกแยะชนิดหรือประเภทของสัญญาณอะไรมากมายแต่ในปัจจุบันการพัฒนาของระบบปฏิบัติการลีนุกซ์มีความซับซ้อนมากขึ้น รวมทั้งการสื่อสารระหว่างโปรเซสก็ต้องการเงื่อนไขที่หลากหลายมากขึ้นเช่นกัน จึงทำให้มีการกำหนดประเภทของสัญญาณ (signal type) ขึ้นมาโดยมีชนิดเป็นเลขจำนวนเต็ม (integer value) ตัวอย่างเช่น เลข 15 (SIGTERM) แทนสัญญาณสิ้นสุดการทำงาน (Terminate) ซึ่งหมายเลขสัญญาณต่างๆจะถูกกำหนดอยู่ในฟังก์ชัน signal.h

1) SIGHUP

2) SIGINT

3) SIGQUIT

4) SIGILL

5) SIGTRAP

6) SIGABRT

7) SIGBUS

8) SIGFPE

9) SIGKILL

10) SIGUSR1

11) SIGSEGV

12) SIGUSR2

13) SIGPIPE

14) SIGALRM

15) SIGTERM

16) SIGSTKFLT

17) SIGCHLD

18) SIGCONT

19) SIGSTOP

20) SIGTSTP

21) SIGTTIN

22) SIGTTOU

23) SIGCONT

24) SIGXCPU

25) SIGXFSZ

26) SIGVTALRM

27) SIGPROF

28) SIGWINCH

29) SIGIO

30) SIGPWR

31) SIGSYS

34) SIGRTMIN

35) SIGRTMIN+1

36) SIGRTMIN+2

37) SIGRTMIN+3

38) SIGRTMIN+4

39) SIGRTMIN+5

40) SIGRTMIN+6

41) SIGRTMIN+7

42) SIGRTMIN+8

43) SIGRTMIN+9

44) SIGRTMIN+10

45) SIGRTMIN+11

46) SIGRTMIN+12

47) SIGRTMIN+13

48) SIGRTMIN+14

49) SIGRTMIN+15

50) SIGRTMAX-14

51) SIGRTMAX-13

52) SIGRTMAX-12

53) SIGRTMAX-11

54) SIGRTMAX-10

55) SIGRTMAX-9

56) SIGRTMAX-8

57) SIGRTMAX-7

58) SIGRTMAX-6

59) SIGRTMAX-5

60) SIGRTMAX-4

61) SIGRTMAX-3

62) SIGRTMAX-2

63) SIGRTMAX-1

64) SIGRTMAX

ตัวอย่างการส่งสัญญาณผ่านคีย์บอร์ด

Ctrl-C

ส่งสัญญาณ INT (SIGINT) ไปยังโปรเซสที่ทำงานอยู่ยุติการทำงานทันที

Ctrl-Z

ส่งสัญญาณ TSTP (SIGTSTP) ไปยังโปรเซสที่ทำงานอยู่ให้หยุดชั่วคราว

Ctrl-\

ส่งสัญญาณ ABRT (SIGABRT) ไปยังโปรเซสที่ทำงานอยู่ให้ยุติการทำงานของโปรเซสนั้นทันที เหมือน Ctrl-C แต่เป็นคำสั่งที่ดีกว่าคือสามารถแก้ไขได้

นอกจากนั้นจะมีการเรียกใช้ฟังก์ชัน kill() เพื่อทำการส่งสัญญาณออกไปยังโปรเซสปลายทาง โดยที่โปรเซสรับปลายทางจะมีการกำหนดไว้ว่าถ้าได้รับสัญญาณหมายเลขนี้ ก็ให้ดำเนินการเรียกฟังก์ชันที่กำหนดไว้ต่อไป ด้วยการเรียกใช้ฟังก์ชัน signal() อย่างไรก็ตามโปรเซสต่างๆที่อยู่ภายใต้ผู้ใช้คนเดียวกัน จะสามารถส่งสัญญาณถึงกันได้ แต่ไม่สามารถส่งสัญญาณไปยังโปรเซสอื่นๆของผู้ใช้คนอื่นได้ ยกเว้นโปรเซสของ superuser เท่านั้น

ฟังก์ชันและตัวแปรที่เกี่ยวข้อง

  • ฟังก์ชัน void signal(int sig_type, void (*sig_handler)(int signal_type))

    • sig_type แทนค่าของสัญญาณที่จะตรวจจับ (ดูจากไฟล์ signal.h)

      • sig_handles แทนค่าตัวชี้ (pointer) ไปยังฟังก์ชันที่สร้างขึ้นมาเพื่อดำเนินการเมื่อได้รับสัญญาณที่ตรวจจับได้

  • ฟังก์ชัน int kill (pid_t dest_pid, int sig_type)

    • เป็นฟังก์ชันที่ใช้ในการส่งสัญญาณที่ต้องการ (sig_type) ไปยังโปรเซสปลายทาง (dest_pid) นอกจากนั้นถ้ามีการกำหนดค่า dest_pid เป็น 0 จะหมายถึงการส่งไปยังทุกโปรเซสของผู้ใช้เดียวกัน แต่ถ้ากำหนดค่า dest_pid เป็น -1 เมื่อใดจะหมายถึงการส่งไปยังทุกโปรเซสในระบบ (แต่ใช้ได้เฉพาะเป็น superuser เท่านั้น)

  • ฟังก์ชัน void raise (int sig_type)

    • ใช้ในกรณีที่ต้องการส่งสัญญาณ (sig_type) ให้ตัวเอง

สิ่งที่ควรรู้

  • โปรเซสจะได้รับสัญญาณได้ทั้งผ่านโปรเซสในกลุ่มเดียวกันของผู้ใช้นั้นส่งมาหรือผู้ใช้ในระดับ superuser เป็นผู้ส่งมาให้เอง

  • ชนิดของสัญญาณและชื่อของสัญญาณถูกกำหนดอยู่ในไฟล์ signal.h

  • สัญญาณที่ถูกส่งไปจะไม่สามารถถูกเก็บเข้าคิวได้

  • ตัวจัดการที่ใช้ในการสิ้นสุดโปรเซสคือ SIG_DFL และ SIG_IGN ในกรณีต้องการเพิกเฉยต่อสัญญาณที่เข้ามา ตัวอย่างเช่นsignal (SIGINT, SIG_IGN);

ตัวอย่างที่ 1

แสดงตัวอย่างการกำหนดฟังก์ชันที่จะให้ดำเนินการเมื่อตรวจจับสัญญาณที่กำหนดไว้ ซึ่งในตัวอย่างนี้จะตรวจสอบสัญญาณหมายเลข 2 (SIGINT)

// signal1.c
#include<stdio.h>
#include<signal.h>
#include<unistd.h>

void signalHandler(int signo) {
	if (signo == SIGINT)
		printf(" received SIGINT\n");
}

int main(void) {
	printf("My pid is %d.\n", getpid());
	if (signal(SIGINT, signalHandler) == SIG_ERR)
		printf("\n Can't catch SIGINT\n");
// A long long wait so that we can easily issue a signal to this process
	while (1)
		sleep(1);
	return 0;
}
$ gcc -o signal1 signal1.c -Wall
$ ./signal1 
My pid is 21231

(ให้กด Ctrl+C เพื่อส่งสัญญาณให้กับโปรแกรม)

^C received SIGINT
^C received SIGINT
^C received SIGINT
^C received SIGINT
^C received SIGINT
^C received SIGINT

(ให้กด Ctrl+Z เพื่อส่งสัญญาณให้กับโปรแกรม)
^Z
[1]+  Stopped                 ./signal1

ตัวอย่างที่ 2

แสดงตัวอย่างโปรเซสลูกจะวนรอรับ signal ที่จะถูกส่งมาจากผู้ใช้เช่น Ctrl+C เป็นต้น โดยโปรเซสแม่จะรอโปรเซสลูกจนกว่าจะทำงานสิ้นสุด

// signal2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>

void HandlerChild() {
	printf("Sigint received by child \n");
}

void HandlerParent() {
	printf("Sigint received by Parent \n");
	signal(SIGINT, HandlerParent);
}

int n, statusp;

int main(void) {
// signal(SIGINT,HandlerParent);
	n = fork();
	if (n == 0) {
		printf("Child ID %d\n", getpid());
		signal(SIGINT, HandlerChild);
		for (;;) {
		};
	} else {
		printf("Parent ID %d \n", getpid());
// signal(SIGINT,HandlerParent);
		wait(&statusp);
// kill(n,SIGKILL);
		exit(0);
	}
	return 0;
}
$ gcc -o signal2 signal2.c -Wall 
$ ./signal2
Parent ID 4564 
Child ID 4565
^CSigint received by child 

ตัวอย่างที่ 3

แสดงตัวอย่างการใช้ฟังก์ชัน alarm(5) เพื่อสร้างสัญญาณขึ้นทุกๆ 5 วินาที

// signal3.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int r, count = 0, n;

void HL_AlarmSig(int al) {
	printf(" I am active every 5 sec ");

	signal(SIGALRM, HL_AlarmSig);
	r = alarm(5);
	count++;
	printf(" .... Now the time is %d sec \n", count * 5);
}

void HL_AllSig(int s) {
	printf(" Oh ! I just received signal number %d \n", s);
	signal(s, HL_AllSig);
}

int main(void) {
	for (n = 1; n <= 31; n++) {
		signal(n, HL_AllSig);
	}
	signal(SIGALRM, HL_AlarmSig);
	r = alarm(5);
	while (count < 12) {
	};
	printf(" Program Terminate !! ");
	return 0;
}
$ gcc -o signal3 signal3.c -Wall 

$ ./signal3
^C Oh ! I just received signal number 2 
^C Oh ! I just received signal number 2 
^C Oh ! I just received signal number 2 
 I am active every 5 sec  .... Now the time is 5 sec 
^Z Oh ! I just received signal number 20 
^\ Oh ! I just received signal number 3 
 I am active every 5 sec  .... Now the time is 10 sec 
 I am active every 5 sec  .... Now the time is 15 sec 
^C Oh ! I just received signal number 2 
^C Oh ! I just received signal number 2 
^C Oh ! I just received signal number 2 
^Z Oh ! I just received signal number 20 
^Z Oh ! I just received signal number 20 
 I am active every 5 sec  .... Now the time is 20 sec 
 I am active every 5 sec  .... Now the time is 25 sec 
 I am active every 5 sec  .... Now the time is 30 sec 


 I am active every 5 sec  .... Now the time is 35 sec 
 I am active every 5 sec  .... Now the time is 40 sec 
 I am active every 5 sec  .... Now the time is 45 sec 
 I am active every 5 sec  .... Now the time is 50 sec 
 I am active every 5 sec  .... Now the time is 55 sec 
 I am active every 5 sec  .... Now the time is 60 sec 

จากผลการทำงานของโปรแกรมข้างต้นเมื่อมีการทำงานของคำสั่ง alarm(5) ที่มีการสร้างสัญญาณขึ้นทุกๆ 5 วินาที แล้วทำให้มีการเรียกฟังก์ชัน HL_AlarmSig() เพื่อแสดงข้อความนับเวลา แล้วเพิ่มค่าตัวแปร count ขึ้นไปเรื่อยๆ จนกระทั่งเมื่อค่าในตัวแปร count เพิ่มขึ้นจนเท่ากับหรือมากกว่า 12 โปรเซสแม่ก็จะสิ้นสุดการทำงานของตัวเอง ดังนั้นเมื่อรวมเวลาที่นับแล้วเท่ากับ 60 วินาที (12 x 5) นั่นเอง

ตัวอย่างที่ 4

แสดงตัวอย่างโปรแกรมตรวจสอบสัญญาณตามที่กำหนดเพื่อเรียกไปยังฟังก์ชันที่กำหนดไว้

// signal4.c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int r, n;

void HL_AlarmSig(int al) {
	printf(" Every 5 sec I have to tell you this PID \n");
	printf(" ....... THIS PROCESS ID = %d ....... \n", getpid());
	signal(SIGALRM, HL_AlarmSig);
	r = alarm(5);

}
void Nothing(int s) {
	signal(s, Nothing);
}

void InfiniteLoop(int s) {
	printf(" No ! I don't care signal number %d  \n", s);
	printf("If you want to stop this process send signal number 9 ONLY!!!\n");

	signal(s, InfiniteLoop);
	for (;;) {
		printf("Loop\n");
	}; /* infinite loop */

}

int main(void) {

	for (n = 1; n <= 31; n++) {
		signal(n, Nothing);
	}
	signal(SIGINT, InfiniteLoop);
	signal(SIGALRM, HL_AlarmSig);
	printf(" If you want to go into the infinite loop \n");
	printf("  .......press  ^C within 5 sec...... \n ");
	r = alarm(5);
	for (;;) {
	};
	printf(" Program Terminate !! \n");
	return 0;
}
$ gcc -o signal4 signal4.c -Wall
$ ./signal4 
If you want to go into the infinite loop
  .......press  ^C within 5 sec......
  Every 5 sec I have to tell you this PID
 ....... THIS PROCESS ID = 21538 .......
 Every 5 sec I have to tell you this PID
 ....... THIS PROCESS ID = 21538 .......

(ถ้ากด Ctrl+C โปรแกรมจะเข้าลูปอนันต์)

ตัวอย่างที่ 5

แสดงตัวอย่างโปรแกรม

// signal5.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

int n, status;

void HL_Child(int s) {
	printf("**Child**I received signal No. %d .. you can't kill me \n", s);
	signal(s, HL_Child);
}

void HL_Parent(int s) {
	printf("***Parent*** I received signal number %d \n", s);
	signal(s, HL_Parent);
}
void HL_Parent14(int s) {
	printf("***Parent*** I received signal No. %d == Alarm Signal \n", s);
	signal(s, HL_Parent14);

}

void HL_Child14(int s) {
	printf("***Child*** I received signal No %d == Alarm Signal \n", s);
	signal(s, HL_Child14);
}
void HL_Child3(int s) {
	printf("***Child*** I received signal No %d  .. I will exit \n", s);
	exit(5);
}

int main(void) {
	int i;
	for (i = 1; i <= 31; i++)
		signal(i, HL_Parent);
	n = fork();
	if (n == 0) /* Child Process */
	{
		for (i = 1; i <= 31; i++)
			signal(i, HL_Child);
		signal(SIGALRM, HL_Child14);
		signal(3, HL_Child3);
		alarm(2);
		printf("--c--  Child ID  = %d  \n", getpid());
		printf("--c--  Child Process goes into the infinite loop \n");
		printf("--c--  Signal number 3 (^\\) can kill me \n");
		for (;;) {
		};
	} else /* Parent Process */
	{
		printf("--p--  Parent ID  = %d  \n", getpid());
		signal(SIGALRM, HL_Parent14);
		/* alarm(2);  */
		printf("--p--  I am waiting for child exit or any signal \n");
		wait(&status);
		printf("--p--  Wait completed !!  and I am waiting again ...\n");
		wait(&status);
		printf("--p--  Wait completed !! ...exit status = %d\n", status >> 8);
		printf("--p-- I am pausing for check signal from child exit ... \n");
		pause();
		wait(&status);
		printf("--p-- Wait completed !! ...exit status = %d \n", status >> 8);
		printf("--p-- I will execute newprog now !! \n");
		for (i = 1; i <= 31; i++)
			signal(i, SIG_IGN);
		signal(3, SIG_DFL);
		signal(2, HL_Parent);
		i = execl("newprog", "newprog", 0);
		printf("--p-- Parent Process terminated !!\n");
		exit(0);

	}
	return 0;
}
$ gcc -o signal5 signal5.c -Wall
$ ./signal5 
--p--  Parent ID  = 22119
--p--  I am waiting for child exit or any signal
--c--  Child ID  = 22120
--c--  Child Process goes into the infinite loop
--c--  Signal number 3 (^\) can kill me

(กด Ctrl+C เพื่อส่งสัญญาณให้ child process)
^C***Parent*** I received signal number 2
**Child**I received signal No. 2 .. you can't kill me

(กด Ctrl+C เพื่อส่งสัญญาณให้ child process)
^C***Parent*** I received signal number 2
**Child**I received signal No. 2 .. you can't kill me
***Child*** I received signal No 14 == Alarm Signal

(กด Ctrl+Z เพื่อส่งสัญญาณให้ child process)
^Z**Child**I received signal No. 18 .. you can't kill me
***Parent*** I received signal number 18

(ถ้ากด Ctrl+\ เพื่อส่งสัญญาณให้ child process เพื่อเรียกฟังก์ชัน HL_Child3() )
^\***Parent*** I received signal number 3
***Child*** I received signal No 3  .. I will exit
***Parent*** I received signal number 20
--p--  Wait completed !!  and I am waiting again ...
--p--  Wait completed !! ...exit status = 5
--p-- I am pausing for check signal from child exit ...

(กด Ctrl+C เพื่อสั่งหยุดโปรแกรม)
^C***Parent*** I received signal number 2
--p-- Wait completed !! ...exit status = 5
--p-- I will execute newprog now !!
--p-- Parent Process terminated !!

ตัวอย่างที่ 6

แสดงตัวอย่างโปรแกรมที่ใช้สำหรับแก้ปัญหาการจัดการคิวของการสิ้นสุดของโปรเซสลูกที่แตกต่างกรณีกัน (exit statues) ซึ่งเมื่อไหร่ก็ตามที่สถานะของโปรเซสลูกเปลี่ยนแปลงโปรเซสแม่ก็จะได้รับสัญญาณ SIGCHLD

/*
 *  signal6.c
 *  
 *  The parent process pulls a child exit status whenever the OS notifies
 *  a child status change.
 *
 *  Created by Mij <mij@bitchx.it> on 04/01/05.
 *  Original source file available at http://mij.oltrelinux.com/devel/unixprg/
 *
 */

/* for printf() and fgetc() */
#include <stdio.h>
/* for fork() */
#include <sys/types.h>
#include <unistd.h>
/* for srandom() and random() */
#include <stdlib.h>
/* for time() [seeding srandom()] */
#include <time.h>
/* for waitpid() */
#include <sys/wait.h>
/* for signal(), kill() and raise() */
#include <signal.h>

/* how many childs to raise */
#define NUM_PROCS   5

/* handler prototype for SIGCHLD */
void child_handler(int);

int main(int argc, char *argv[]) {
	int i, exit_status;

	/* execute child_handler() when receiving a signal of type SIGCHLD */
	signal(SIGCHLD, &child_handler);

	/* initialize the random num generator */
	srandom(time(NULL));

	printf("Try to issue a \'ps\' while the process is running...\n");

	/* produce NUM_PROCS childs */
	for (i = 0; i < NUM_PROCS; i++) {
		if (!fork()) {
			/* child */

			/* choosing a random exit status between 0 and 99 */
			exit_status = (int) (random() % 100);
			printf(" -> New child %d, will exit with %d.\n", (int) getpid(),
					exit_status);

			/* try to skip signals overlapping */
			sleep((unsigned int) (random() % 3));

			/* choosing a value to exit between 0 and 99 */
			exit(exit_status);
		}

		/* father */
		sleep((unsigned int) (random() % 2));
	}

	/* checkpoint */
	printf("parent: done with fork()ing.\n");

	/* why this is not equivalent to sleep(20) here? */
	for (i = 0; i < 10; i++) {
		sleep(1);
	}
	/* all the child processes should be done now */
	printf("I did not purge all the childs. Timeout; exiting.\n");

	/* terminate myself => exit */
	kill(getpid(), 15);

	/* this won't be actually executed */
	return 0;
}

/* handler definition for SIGCHLD */
void child_handler(int sig_type) {
	int child_status;
	pid_t child;
	static int call_num = 0;

	/* getting the child's exit status */
	child = waitpid(0, &child_status, 0);

	printf("<-  Child %d exited with status %d.\n", child,
			WEXITSTATUS(child_status));

	/* did we get all the childs? */
	if (++call_num >= NUM_PROCS) {
		printf("I got all the childs this time. Going to exit.\n");
		exit(0);
	}

	return;
}
$ gcc -o signal6 signal6.c -Wall

$ ./signal6 
Try to issue a 'ps' while the process is running...
 -> New child 12274, will exit with 68.
 -> New child 12275, will exit with 57.
 -> New child 12276, will exit with 96.
parent: done with fork()ing.
 -> New child 12277, will exit with 18.
 -> New child 12278, will exit with 72.
<-  Child 12275 exited with status 57.
<-  Child 12274 exited with status 68.
<-  Child 12277 exited with status 18.
<-  Child 12276 exited with status 96.
<-  Child 12278 exited with status 72.
I got all the childs this time. Going to exit.

Last updated

Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University