เนื่องจากprintf
ไม่ได้รับการส่งกลับจึงไม่ควรใช้ในเครื่องจัดการสัญญาณอย่างปลอดภัย แต่ฉันเคยเห็นโค้ดตัวอย่างมากมายที่ใช้printf
วิธีนี้
ดังนั้นคำถามของฉันคือเมื่อใดที่เราต้องหลีกเลี่ยงการใช้printf
ในเครื่องจัดการสัญญาณและมีการแนะนำให้เปลี่ยนหรือไม่
เนื่องจากprintf
ไม่ได้รับการส่งกลับจึงไม่ควรใช้ในเครื่องจัดการสัญญาณอย่างปลอดภัย แต่ฉันเคยเห็นโค้ดตัวอย่างมากมายที่ใช้printf
วิธีนี้
ดังนั้นคำถามของฉันคือเมื่อใดที่เราต้องหลีกเลี่ยงการใช้printf
ในเครื่องจัดการสัญญาณและมีการแนะนำให้เปลี่ยนหรือไม่
คำตอบ:
คุณสามารถใช้ตัวแปรแฟล็กตั้งค่าแฟล็กนั้นภายในตัวจัดการสัญญาณและใช้printf()
ฟังก์ชันการเรียกแฟล็กใน main () หรือส่วนอื่น ๆ ของโปรแกรมในระหว่างการทำงานปกติ
ไม่ปลอดภัยที่จะเรียกใช้ฟังก์ชันทั้งหมดเช่น
printf
จากภายในตัวจัดการสัญญาณ เทคนิคที่มีประโยชน์คือการใช้ตัวจัดการสัญญาณเพื่อตั้งค่าflag
และจากนั้นตรวจสอบflag
จากโปรแกรมหลักและพิมพ์ข้อความหากจำเป็น
สังเกตในตัวอย่างด้านล่างตัวจัดการสัญญาณ ding () ตั้งค่าแฟalarm_fired
ล็กเป็น 1 เนื่องจาก SIGALRM จับได้และในalarm_fired
ค่าฟังก์ชันหลักจะถูกตรวจสอบเพื่อเรียกใช้ printf ตามเงื่อนไขอย่างถูกต้อง
static int alarm_fired = 0;
void ding(int sig) // can be called asynchronously
{
alarm_fired = 1; // set flag
}
int main()
{
pid_t pid;
printf("alarm application starting\n");
pid = fork();
switch(pid) {
case -1:
/* Failure */
perror("fork failed");
exit(1);
case 0:
/* child */
sleep(5);
kill(getppid(), SIGALRM);
exit(0);
}
/* if we get here we are the parent process */
printf("waiting for alarm to go off\n");
(void) signal(SIGALRM, ding);
pause();
if (alarm_fired) // check flag to call printf
printf("Ding!\n");
printf("done\n");
exit(0);
}
เอกสารอ้างอิง: Beginning Linux Programming, 4th Edition , ในหนังสือเล่มนี้มีการอธิบายโค้ดของคุณอย่างตรงประเด็น (สิ่งที่คุณต้องการ), บทที่ 11: กระบวนการและสัญญาณ, หน้า 484
นอกจากนี้คุณต้องใช้ความระมัดระวังเป็นพิเศษในการเขียนฟังก์ชันตัวจัดการเนื่องจากสามารถเรียกแบบอะซิงโครนัสได้ นั่นคือตัวจัดการอาจถูกเรียก ณ จุดใดก็ได้ในโปรแกรมโดยไม่สามารถคาดเดาได้ หากสัญญาณสองสัญญาณมาถึงในช่วงเวลาที่สั้นมากตัวจัดการหนึ่งสามารถทำงานภายในอีกเครื่องหนึ่งได้ และถือเป็นแนวทางปฏิบัติที่ดีกว่าในการประกาศvolatile sigatomic_t
ประเภทนี้จะเข้าถึงได้ตลอดเวลาโดยหลีกเลี่ยงความไม่แน่นอนเกี่ยวกับการขัดขวางการเข้าถึงตัวแปร (อ่าน: การเข้าถึงข้อมูลปรมาณูและการจัดการสัญญาณสำหรับการขยายรายละเอียด)
อ่านการกำหนดเครื่องจัดการสัญญาณ : เพื่อเรียนรู้วิธีการเขียนฟังก์ชันตัวจัดการสัญญาณที่สามารถสร้างขึ้นด้วยฟังก์ชันsignal()
หรือ
รายการฟังก์ชั่นที่ได้รับอนุญาตในหน้าคู่มือการเรียกใช้ฟังก์ชันนี้ภายในตัวจัดการสัญญาณนั้นปลอดภัยsigaction()
volatile sigatomic_t alarm_fired;
ปัญหาหลักคือถ้าสัญญาณขัดจังหวะmalloc()
หรือฟังก์ชันบางอย่างที่คล้ายกันสถานะภายในอาจไม่สอดคล้องกันชั่วคราวในขณะที่กำลังย้ายบล็อกหน่วยความจำระหว่างรายการที่ว่างและที่ใช้หรือการดำเนินการอื่นที่คล้ายคลึงกัน หากรหัสในตัวจัดการสัญญาณเรียกใช้ฟังก์ชันที่เรียกใช้การดำเนินการmalloc()
นี้อาจทำให้การจัดการหน่วยความจำเสียหายโดยสิ้นเชิง
มาตรฐาน C คำนึงถึงสิ่งที่คุณสามารถทำได้ในเครื่องจัดการสัญญาณอย่างระมัดระวัง:
ISO / IEC 9899: 2011 §7.14.1.1
signal
ฟังก์ชัน¶5หากสัญญาณเกิดขึ้นนอกเหนือจากผลของการเรียกใช้
abort
หรือraise
ฟังก์ชันลักษณะการทำงานจะไม่ถูกกำหนดหากตัวจัดการสัญญาณอ้างถึงวัตถุใด ๆ ที่มีระยะเวลาการจัดเก็บแบบคงที่หรือเธรดที่ไม่ใช่วัตถุอะตอมที่ไม่มีการล็อคนอกเหนือจากการกำหนดค่า ไปยังวัตถุที่ประกาศเป็นvolatile sig_atomic_t
หรือตัวจัดการสัญญาณเรียกใช้ฟังก์ชันใด ๆ ในไลบรารีมาตรฐานนอกเหนือจากabort
ฟังก์ชัน_Exit
ฟังก์ชันquick_exit
ฟังก์ชันหรือsignal
ฟังก์ชันที่มีอาร์กิวเมนต์แรกเท่ากับหมายเลขสัญญาณที่ตรงกับสัญญาณที่ทำให้เกิดการเรียกใช้ ตัวจัดการ. นอกจากนี้หากการเรียกใช้signal
ฟังก์ชันดังกล่าวSIG_ERR
ส่งผลกลับค่าของerrno
จะไม่แน่นอน 252)252)หากสัญญาณใด ๆ ถูกสร้างขึ้นโดยตัวจัดการสัญญาณแบบอะซิงโครนัสพฤติกรรมนั้นจะไม่ได้กำหนดไว้
POSIX มีความใจกว้างมากขึ้นเกี่ยวกับสิ่งที่คุณสามารถทำได้ในเครื่องจัดการสัญญาณ
แนวคิดสัญญาณในรุ่น POSIX 2008 กล่าวว่า:
หากกระบวนการเป็นแบบมัลติเธรดหรือหากกระบวนการเป็นเธรดเดียวและตัวจัดการสัญญาณถูกดำเนินการนอกเหนือจากผลลัพธ์ของ:
เรียกกระบวนการ
abort()
,raise()
,kill()
,pthread_kill()
หรือsigqueue()
เพื่อสร้างสัญญาณที่ไม่ได้ถูกบล็อกสัญญาณที่รอดำเนินการถูกยกเลิกการปิดกั้นและถูกส่งก่อนการโทรที่ยกเลิกการปิดกั้นจะกลับมา
ลักษณะการทำงานไม่ได้กำหนดไว้หากตัวจัดการสัญญาณอ้างถึงวัตถุใด ๆ นอกเหนือ
errno
จากระยะเวลาการจัดเก็บแบบคงที่นอกเหนือจากการกำหนดค่าให้กับวัตถุที่ประกาศเป็นvolatile sig_atomic_t
หรือหากตัวจัดการสัญญาณเรียกใช้ฟังก์ชันใด ๆ ที่กำหนดไว้ในมาตรฐานนี้นอกเหนือจากฟังก์ชันใดฟังก์ชันหนึ่งที่ระบุไว้ใน ตารางต่อไปนี้ตารางต่อไปนี้กำหนดชุดของฟังก์ชันที่ต้องปลอดภัยแบบ async-signal ดังนั้นแอปพลิเคชันสามารถเรียกใช้งานได้โดยไม่มีข้อ จำกัด จากฟังก์ชันจับสัญญาณ:
_Exit() fexecve() posix_trace_event() sigprocmask() _exit() fork() pselect() sigqueue() … fcntl() pipe() sigpause() write() fdatasync() poll() sigpending()
ฟังก์ชันทั้งหมดที่ไม่อยู่ในตารางด้านบนจะถือว่าไม่ปลอดภัยเมื่อเทียบกับสัญญาณ ในที่ที่มีสัญญาณฟังก์ชันทั้งหมดที่กำหนดโดยโวลุ่ม POSIX.1-2008 นี้จะต้องทำงานตามที่กำหนดไว้เมื่อถูกเรียกหรือถูกขัดจังหวะโดยฟังก์ชันจับสัญญาณโดยมีข้อยกเว้นเพียงข้อเดียวคือเมื่อสัญญาณขัดจังหวะฟังก์ชันที่ไม่ปลอดภัยและสัญญาณ - ฟังก์ชั่นการจับเรียกฟังก์ชันที่ไม่ปลอดภัยพฤติกรรมไม่ได้กำหนดไว้
การดำเนินการที่ได้รับมูลค่า
errno
และการดำเนินการที่กำหนดค่าให้errno
จะต้องปลอดภัยแบบ async-signalเมื่อสัญญาณถูกส่งไปยังเธรดหากการกระทำของสัญญาณนั้นระบุถึงการยุติหยุดหรือดำเนินการต่อกระบวนการทั้งหมดจะถูกยกเลิกหยุดหรือดำเนินการต่อตามลำดับ
อย่างไรก็ตามprintf()
ตระกูลของฟังก์ชันนั้นขาดหายไปจากรายการนั้นและอาจไม่ถูกเรียกอย่างปลอดภัยจากเครื่องจัดการสัญญาณ
การอัปเดตPOSIX 2016ขยายรายการฟังก์ชันที่ปลอดภัยเพื่อรวมไว้โดยเฉพาะฟังก์ชันจำนวนมาก<string.h>
ซึ่งเป็นส่วนเพิ่มเติมที่มีคุณค่าอย่างยิ่ง (หรือเป็นการกำกับดูแลที่น่าหงุดหงิดโดยเฉพาะ) รายการตอนนี้:
_Exit() getppid() sendmsg() tcgetpgrp()
_exit() getsockname() sendto() tcsendbreak()
abort() getsockopt() setgid() tcsetattr()
accept() getuid() setpgid() tcsetpgrp()
access() htonl() setsid() time()
aio_error() htons() setsockopt() timer_getoverrun()
aio_return() kill() setuid() timer_gettime()
aio_suspend() link() shutdown() timer_settime()
alarm() linkat() sigaction() times()
bind() listen() sigaddset() umask()
cfgetispeed() longjmp() sigdelset() uname()
cfgetospeed() lseek() sigemptyset() unlink()
cfsetispeed() lstat() sigfillset() unlinkat()
cfsetospeed() memccpy() sigismember() utime()
chdir() memchr() siglongjmp() utimensat()
chmod() memcmp() signal() utimes()
chown() memcpy() sigpause() wait()
clock_gettime() memmove() sigpending() waitpid()
close() memset() sigprocmask() wcpcpy()
connect() mkdir() sigqueue() wcpncpy()
creat() mkdirat() sigset() wcscat()
dup() mkfifo() sigsuspend() wcschr()
dup2() mkfifoat() sleep() wcscmp()
execl() mknod() sockatmark() wcscpy()
execle() mknodat() socket() wcscspn()
execv() ntohl() socketpair() wcslen()
execve() ntohs() stat() wcsncat()
faccessat() open() stpcpy() wcsncmp()
fchdir() openat() stpncpy() wcsncpy()
fchmod() pause() strcat() wcsnlen()
fchmodat() pipe() strchr() wcspbrk()
fchown() poll() strcmp() wcsrchr()
fchownat() posix_trace_event() strcpy() wcsspn()
fcntl() pselect() strcspn() wcsstr()
fdatasync() pthread_kill() strlen() wcstok()
fexecve() pthread_self() strncat() wmemchr()
ffs() pthread_sigmask() strncmp() wmemcmp()
fork() raise() strncpy() wmemcpy()
fstat() read() strnlen() wmemmove()
fstatat() readlink() strpbrk() wmemset()
fsync() readlinkat() strrchr() write()
ftruncate() recv() strspn()
futimens() recvfrom() strstr()
getegid() recvmsg() strtok_r()
geteuid() rename() symlink()
getgid() renameat() symlinkat()
getgroups() rmdir() tcdrain()
getpeername() select() tcflow()
getpgrp() sem_post() tcflush()
getpid() send() tcgetattr()
ด้วยเหตุนี้คุณอาจใช้งานwrite()
โดยไม่ได้รับการสนับสนุนการจัดรูปแบบโดยprintf()
et al หรือคุณจะตั้งค่าสถานะที่คุณทดสอบ (เป็นระยะ ๆ ) ในตำแหน่งที่เหมาะสมในโค้ดของคุณ เทคนิคนี้จะแสดงให้เห็นถึงความสามารถในคำตอบโดยGrijesh ชัวฮาน
chqrlie ถามคำถามที่น่าสนใจซึ่งฉันมีคำตอบไม่เกินบางส่วน:
ฟังก์ชันสตริงส่วนใหญ่มาจากฟังก์ชัน
<string.h>
คลาสอักขระ<ctype.h>
และฟังก์ชันไลบรารีมาตรฐาน C อื่น ๆ อีกมากมายไม่อยู่ในรายการด้านบนได้อย่างไร การนำไปใช้งานจะต้องมีเจตนาชั่วร้ายที่จะทำให้strlen()
ไม่ปลอดภัยในการโทรจากตัวจัดการสัญญาณ
หลายฟังก์ชั่นใน<string.h>
มันเป็นเรื่องยากที่จะเห็นว่าทำไมพวกเขาไม่ได้ประกาศปลอดภัย async สัญญาณและฉันยอมรับstrlen()
เป็นตัวอย่างที่สำคัญพร้อมกับstrchr()
, strstr()
ฯลฯ บนมืออื่น ๆ , ฟังก์ชั่นอื่น ๆ เช่นstrtok()
, strcoll()
และstrxfrm()
ค่อนข้างซับซ้อนและไม่น่าจะปลอดภัยแบบ async-signal เนื่องจากstrtok()
ยังคงสถานะระหว่างการโทรและตัวจัดการสัญญาณไม่สามารถบอกได้อย่างง่ายดายว่าบางส่วนของรหัสที่ใช้strtok()
จะยุ่งเหยิงหรือไม่ strcoll()
และstrxfrm()
ฟังก์ชั่นการทำงานกับข้อมูลสถานที่สำคัญและสถานที่เกิดเหตุโหลดเกี่ยวข้องกับทุกประเภทของการตั้งค่าของรัฐ
ฟังก์ชั่น (มาโคร) จาก<ctype.h>
ที่มีทั้งหมดสถานที่สำคัญและดังนั้นจึงสามารถใช้เป็นประเด็นเดียวกับและstrcoll()
strxfrm()
ฉันพบว่ามันยากที่จะเห็นว่าเหตุใดฟังก์ชันทางคณิตศาสตร์จาก<math.h>
จึงไม่ปลอดภัยแบบ async-signal เว้นแต่จะเป็นเพราะอาจได้รับผลกระทบจาก SIGFPE (ข้อยกเว้นจุดลอยตัว) แม้ว่าในช่วงเวลาเดียวที่ฉันเห็นว่าหนึ่งในวันนี้เป็นจำนวนเต็มการหารด้วยศูนย์. ความไม่แน่นอนที่คล้ายกันเกิดขึ้นจาก<complex.h>
, และ<fenv.h>
<tgmath.h>
บางฟังก์ชันใน<stdlib.h>
อาจได้รับการยกเว้น - abs()
ตัวอย่างเช่น คนอื่น ๆ มีปัญหาโดยเฉพาะ: malloc()
และครอบครัวเป็นตัวอย่างที่สำคัญ
สามารถทำการประเมินที่คล้ายกันสำหรับส่วนหัวอื่น ๆ ใน Standard C (2011) ที่ใช้ในสภาพแวดล้อม POSIX (มาตรฐาน C มีข้อ จำกัด มากดังนั้นจึงไม่มีความสนใจในการวิเคราะห์ในสภาพแวดล้อม Standard C ที่บริสุทธิ์) สิ่งที่ทำเครื่องหมาย 'ขึ้นอยู่กับสถานที่' ไม่ปลอดภัยเนื่องจากการจัดการโลแคลอาจต้องมีการจัดสรรหน่วยความจำเป็นต้น
<assert.h>
- อาจไม่ปลอดภัย<complex.h>
- อาจปลอดภัย<ctype.h>
- ไม่ปลอดภัย<errno.h>
- ปลอดภัย<fenv.h>
- อาจไม่ปลอดภัย<float.h>
- ไม่มีฟังก์ชั่น<inttypes.h>
- ฟังก์ชั่นที่ไวต่อสถานที่ (ไม่ปลอดภัย)<iso646.h>
- ไม่มีฟังก์ชั่น<limits.h>
- ไม่มีฟังก์ชั่น<locale.h>
- ฟังก์ชั่นที่ไวต่อสถานที่ (ไม่ปลอดภัย)<math.h>
- อาจปลอดภัย<setjmp.h>
- ไม่ปลอดภัย<signal.h>
- อนุญาต<stdalign.h>
- ไม่มีฟังก์ชั่น<stdarg.h>
- ไม่มีฟังก์ชั่น<stdatomic.h>
- อาจจะปลอดภัยอาจไม่ปลอดภัย<stdbool.h>
- ไม่มีฟังก์ชั่น<stddef.h>
- ไม่มีฟังก์ชั่น<stdint.h>
- ไม่มีฟังก์ชั่น<stdio.h>
- ไม่ปลอดภัย<stdlib.h>
- ไม่ปลอดภัยทั้งหมด (บางคนได้รับอนุญาตคนอื่นไม่ได้)<stdnoreturn.h>
- ไม่มีฟังก์ชั่น<string.h>
- ไม่ปลอดภัยทั้งหมด<tgmath.h>
- อาจปลอดภัย<threads.h>
- อาจไม่ปลอดภัย<time.h>
- ขึ้นอยู่กับสถานที่ (แต่time()
ได้รับอนุญาตอย่างชัดเจน)<uchar.h>
- ขึ้นอยู่กับสถานที่<wchar.h>
- ขึ้นอยู่กับสถานที่<wctype.h>
- ขึ้นอยู่กับสถานที่การวิเคราะห์ส่วนหัว POSIX น่าจะ ... ยากกว่าเนื่องจากมีจำนวนมากและบางฟังก์ชันอาจปลอดภัย แต่หลายฟังก์ชันจะไม่เป็นเช่นนั้น ... แต่ก็ง่ายกว่าด้วยเพราะ POSIX บอกว่าฟังก์ชันใดปลอดภัยแบบไม่ซิงค์สัญญาณ (มีไม่มากนัก) โปรดทราบว่าส่วนหัวเช่น<pthread.h>
มีฟังก์ชันปลอดภัยสามฟังก์ชันและฟังก์ชันที่ไม่ปลอดภัยมากมาย
หมายเหตุ:การประเมินฟังก์ชันและส่วนหัว C เกือบทั้งหมดในสภาพแวดล้อม POSIX เป็นการคาดเดาแบบกึ่งการศึกษา ไม่มีความรู้สึกว่าเป็นคำกล่าวที่ชัดเจนจากร่างมาตรฐาน
<string.h>
คลาสอักขระ<ctype.h>
และฟังก์ชันไลบรารีมาตรฐาน C อื่น ๆ อีกมากมายไม่อยู่ในรายการด้านบนได้อย่างไร การนำไปใช้งานจะต้องมีเจตนาชั่วร้ายที่จะทำให้strlen()
ไม่ปลอดภัยในการโทรจากตัวจัดการสัญญาณ
<ctype.h>
สิ่งต่างๆนั้นเป็นภาษาเฉพาะและอาจทำให้เกิดปัญหาได้หากสัญญาณขัดจังหวะฟังก์ชันการตั้งค่าโลแคล แต่เมื่อโหลดโลแคลแล้วการใช้ควรปลอดภัย ฉันเดาว่าในสถานการณ์ที่ซับซ้อนบางอย่างการโหลดข้อมูลโลแคลสามารถทำได้ทีละน้อยดังนั้นจึงทำให้ฟังก์ชัน<ctype.h>
ไม่ปลอดภัย ข้อสรุปยังคงอยู่: หากมีข้อสงสัยให้งดเว้น
จะหลีกเลี่ยงการใช้
printf
ในเครื่องจัดการสัญญาณได้อย่างไร?
หลีกเลี่ยงเสมอจะพูดว่า: อย่าใช้printf()
ในเครื่องจัดการสัญญาณ
อย่างน้อยในระบบที่สอดคล้องกับ POSIX คุณสามารถใช้write(STDOUT_FILENO, ...)
แทนprintf()
. การจัดรูปแบบอาจไม่ใช่เรื่องง่ายอย่างไรก็ตามพิมพ์ int จากตัวจัดการสัญญาณโดยใช้ฟังก์ชันเขียนหรือไม่ปลอดภัย
Always avoid it.
หมายถึง? หลีกเลี่ยงprintf()
?
printf()
ในตัวจัดการสัญญาณ
2
จุดตรวจสอบ OP ขอวิธีหลีกเลี่ยงการใช้printf()
ในตัวจัดการสัญญาณ?
เพื่อวัตถุประสงค์ในการดีบักฉันได้เขียนเครื่องมือที่ตรวจสอบว่าคุณกำลังเรียกใช้ฟังก์ชันในasync-signal-safe
รายการเท่านั้นและพิมพ์ข้อความเตือนสำหรับฟังก์ชันที่ไม่ปลอดภัยแต่ละฟังก์ชันที่เรียกว่าภายในบริบทสัญญาณ แม้ว่าจะไม่ช่วยแก้ปัญหาในการเรียกใช้ฟังก์ชันที่ไม่ปลอดภัยจากบริบทสัญญาณ แต่อย่างน้อยก็ช่วยให้คุณพบกรณีที่คุณทำโดยไม่ได้ตั้งใจ
รหัสที่มาเป็นบน GitHub มันทำงานโดยการโอเวอร์โหลดsignal/sigaction
จากนั้นจี้PLT
รายการของฟังก์ชันที่ไม่ปลอดภัยชั่วคราว สิ่งนี้ทำให้การเรียกใช้ฟังก์ชันที่ไม่ปลอดภัยถูกเปลี่ยนเส้นทางไปยัง wrapper
เทคนิคหนึ่งที่มีประโยชน์อย่างยิ่งในโปรแกรมที่มีลูปเลือกคือการเขียนไบต์เดียวลงไปในท่อเมื่อได้รับสัญญาณแล้วจัดการสัญญาณในลูปเลือก บางสิ่งตามบรรทัดเหล่านี้(การจัดการข้อผิดพลาดและรายละเอียดอื่น ๆ ที่ถูกละไว้เพื่อความกะทัดรัด) :
static int sigPipe[2];
static void gotSig ( int num ) { write(sigPipe[1], "!", 1); }
int main ( void ) {
pipe(sigPipe);
/* use sigaction to point signal(s) at gotSig() */
FD_SET(sigPipe[0], &readFDs);
for (;;) {
n = select(nFDs, &readFDs, ...);
if (FD_ISSET(sigPipe[0], &readFDs)) {
read(sigPipe[0], ch, 1);
/* do something about the signal here */
}
/* ... the rest of your select loop */
}
}
ถ้าคุณดูแลซึ่งส่งสัญญาณว่ามันเป็นแล้วไบต์ลงท่อสามารถเป็นตัวเลขสัญญาณ
ใช้ async-signal-safe ของคุณเองsnprintf("%d
และใช้งานwrite
มันไม่แย่อย่างที่คิดวิธีการแปลง int เป็น string ใน C? มีการใช้งานหลายอย่าง
เนื่องจากมีข้อมูลที่น่าสนใจเพียงสองประเภทที่ผู้จัดการสัญญาณสามารถเข้าถึงได้:
sig_atomic_t
ลูกโลกint
อาร์กิวเมนต์สัญญาณโดยพื้นฐานแล้วจะครอบคลุมกรณีการใช้งานที่น่าสนใจทั้งหมด
ความจริงที่ว่าstrcpy
สัญญาณยังปลอดภัยทำให้สิ่งต่างๆดียิ่งขึ้น
โปรแกรม POSIX ด้านล่างจะพิมพ์เป็นจำนวนครั้งที่ได้รับ SIGINT จนถึงตอนนี้ซึ่งคุณสามารถทริกเกอร์ได้Ctrl + C
และ ID สัญญาณ
คุณสามารถออกจากโปรแกรมด้วยCtrl + \
(SIGQUIT)
main.c:
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
/* Calculate the minimal buffer size for a given type.
*
* Here we overestimate and reserve 8 chars per byte.
*
* With this size we could even print a binary string.
*
* - +1 for NULL terminator
* - +1 for '-' sign
*
* A tight limit for base 10 can be found at:
* /programming/8257714/how-to-convert-an-int-to-string-in-c/32871108#32871108
*
* TODO: get tight limits for all bases, possibly by looking into
* glibc's atoi: /programming/190229/where-is-the-itoa-function-in-linux/52127877#52127877
*/
#define ITOA_SAFE_STRLEN(type) sizeof(type) * CHAR_BIT + 2
/* async-signal-safe implementation of integer to string conversion.
*
* Null terminates the output string.
*
* The input buffer size must be large enough to contain the output,
* the caller must calculate it properly.
*
* @param[out] value Input integer value to convert.
* @param[out] result Buffer to output to.
* @param[in] base Base to convert to.
* @return Pointer to the end of the written string.
*/
char *itoa_safe(intmax_t value, char *result, int base) {
intmax_t tmp_value;
char *ptr, *ptr2, tmp_char;
if (base < 2 || base > 36) {
return NULL;
}
ptr = result;
do {
tmp_value = value;
value /= base;
*ptr++ = "ZYXWVUTSRQPONMLKJIHGFEDCBA9876543210123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[35 + (tmp_value - value * base)];
} while (value);
if (tmp_value < 0)
*ptr++ = '-';
ptr2 = result;
result = ptr;
*ptr-- = '\0';
while (ptr2 < ptr) {
tmp_char = *ptr;
*ptr--= *ptr2;
*ptr2++ = tmp_char;
}
return result;
}
volatile sig_atomic_t global = 0;
void signal_handler(int sig) {
char key_str[] = "count, sigid: ";
/* This is exact:
* - the null after the first int will contain the space
* - the null after the second int will contain the newline
*/
char buf[2 * ITOA_SAFE_STRLEN(sig_atomic_t) + sizeof(key_str)];
enum { base = 10 };
char *end;
end = buf;
strcpy(end, key_str);
end += sizeof(key_str);
end = itoa_safe(global, end, base);
*end++ = ' ';
end = itoa_safe(sig, end, base);
*end++ = '\n';
write(STDOUT_FILENO, buf, end - buf);
global += 1;
signal(sig, signal_handler);
}
int main(int argc, char **argv) {
/* Unit test itoa_safe. */
{
typedef struct {
intmax_t n;
int base;
char out[1024];
} InOut;
char result[1024];
size_t i;
InOut io;
InOut ios[] = {
/* Base 10. */
{0, 10, "0"},
{1, 10, "1"},
{9, 10, "9"},
{10, 10, "10"},
{100, 10, "100"},
{-1, 10, "-1"},
{-9, 10, "-9"},
{-10, 10, "-10"},
{-100, 10, "-100"},
/* Base 2. */
{0, 2, "0"},
{1, 2, "1"},
{10, 2, "1010"},
{100, 2, "1100100"},
{-1, 2, "-1"},
{-100, 2, "-1100100"},
/* Base 35. */
{0, 35, "0"},
{1, 35, "1"},
{34, 35, "Y"},
{35, 35, "10"},
{100, 35, "2U"},
{-1, 35, "-1"},
{-34, 35, "-Y"},
{-35, 35, "-10"},
{-100, 35, "-2U"},
};
for (i = 0; i < sizeof(ios)/sizeof(ios[0]); ++i) {
io = ios[i];
itoa_safe(io.n, result, io.base);
if (strcmp(result, io.out)) {
printf("%ju %d %s\n", io.n, io.base, io.out);
assert(0);
}
}
}
/* Handle the signals. */
if (argc > 1 && !strcmp(argv[1], "1")) {
signal(SIGINT, signal_handler);
while(1);
}
return EXIT_SUCCESS;
}
รวบรวมและเรียกใช้:
gcc -std=c99 -Wall -Wextra -o main main.c
./main 1
หลังจากกด Ctrl + C สิบห้าครั้งเทอร์มินัลจะแสดง:
^Ccount, sigid: 0 2
^Ccount, sigid: 1 2
^Ccount, sigid: 2 2
^Ccount, sigid: 3 2
^Ccount, sigid: 4 2
^Ccount, sigid: 5 2
^Ccount, sigid: 6 2
^Ccount, sigid: 7 2
^Ccount, sigid: 8 2
^Ccount, sigid: 9 2
^Ccount, sigid: 10 2
^Ccount, sigid: 11 2
^Ccount, sigid: 12 2
^Ccount, sigid: 13 2
^Ccount, sigid: 14 2
ซึ่งเป็นจำนวนสัญญาณสำหรับ2
SIGINT
ทดสอบบน Ubuntu 18.04 GitHub อัปสตรีม
นอกจากนี้คุณยังสามารถใช้write()
โดยตรงซึ่งเป็นฟังก์ชันปลอดภัยแบบไม่ซิงค์สัญญาณ
#include <unistd.h>
int main(void) {
write(1,"Hello World!", 12);
return 0;
}
คุณสามารถใช้ printf ในตัวจัดการสัญญาณได้หากคุณใช้ไลบรารี pthread unix / posix ระบุว่า printf เป็น atomic สำหรับเธรด cf Dave Butenhof ตอบกลับที่นี่: https://groups.google.com/forum/#!topic/comp.programming.threads/1-bU71nYgqw โปรดทราบว่าเพื่อให้ได้ภาพที่ชัดเจนขึ้น ของเอาต์พุต printf คุณควรเรียกใช้แอปพลิเคชันของคุณในคอนโซล (บน linux ใช้ ctl + alt + f1 เพื่อเริ่มคอนโซล 1) แทนที่จะเป็น pseudo-tty ที่สร้างโดย GUI
printf
สายนั้นในเครื่องส่งสัญญาณหรือไม่? ลบมัน.