จะใช้ตัวจัดการสัญญาณในภาษา C ได้อย่างไร?

How Use Signal Handlers C Language



ในบทความนี้เราจะแสดงวิธีใช้ตัวจัดการสัญญาณใน Linux โดยใช้ภาษา C แต่ก่อนอื่น เราจะพูดถึงว่าสัญญาณคืออะไร จะสร้างสัญญาณทั่วไปบางอย่างได้อย่างไร ซึ่งคุณสามารถใช้ในโปรแกรมของคุณ จากนั้นเราจะดูว่าโปรแกรมสามารถจัดการกับสัญญาณต่างๆ ได้อย่างไรในขณะที่โปรแกรมดำเนินการ เริ่มกันเลย

สัญญาณ

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







สัญญาณมาตรฐาน

สัญญาณถูกกำหนดไว้ในไฟล์ส่วนหัว สัญญาณ.h เป็นค่าคงที่มาโคร ชื่อสัญญาณเริ่มต้นด้วย SIG และตามด้วยคำอธิบายสั้นๆ ของสัญญาณ ดังนั้น ทุกสัญญาณจึงมีค่าตัวเลขที่ไม่ซ้ำกัน โปรแกรมของคุณควรใช้ชื่อของสัญญาณ ไม่ใช่หมายเลขสัญญาณ เหตุผลคือจำนวนสัญญาณอาจแตกต่างกันไปตามระบบ แต่ความหมายของชื่อจะเป็นมาตรฐาน



มาโคร นซิก คือจำนวนสัญญาณที่กำหนดทั้งหมด คุณค่าของ นซิก มีค่ามากกว่าจำนวนสัญญาณที่กำหนดทั้งหมด 1 รายการ (หมายเลขสัญญาณทั้งหมดได้รับการจัดสรรตามลำดับ)



ต่อไปนี้เป็นสัญญาณมาตรฐาน:





ชื่อสัญญาณ คำอธิบาย
SIGHUP วางสายกระบวนการ สัญญาณ SIGHUP ใช้เพื่อรายงานการตัดการเชื่อมต่อของเทอร์มินัลของผู้ใช้ อาจเป็นเพราะการเชื่อมต่อระยะไกลขาดหายไปหรือวางสาย
SIGINT ขัดจังหวะกระบวนการ เมื่อผู้ใช้พิมพ์อักขระ INTR (ปกติคือ Ctrl + C) สัญญาณ SIGINT จะถูกส่งออกไป
SIGQUIT ออกจากกระบวนการ เมื่อผู้ใช้พิมพ์อักขระ QUIT (ปกติคือ Ctrl + ) สัญญาณ SIGQUIT จะถูกส่ง
ผนึก คำสั่งที่ผิดกฎหมาย เมื่อมีการพยายามรันคำสั่งขยะหรือคำสั่งพิเศษ สัญญาณ SIGILL จะถูกสร้างขึ้น นอกจากนี้ สามารถสร้าง SIGILL ได้เมื่อสแต็กโอเวอร์โฟลว์ หรือเมื่อระบบมีปัญหาในการรันตัวจัดการสัญญาณ
ซิกแทรป กับดักติดตาม คำสั่งเบรกพอยต์และคำสั่งกับดักอื่นๆ จะสร้างสัญญาณ SIGTRAP ดีบักเกอร์ใช้สัญญาณนี้
SIGABRT ยกเลิก สัญญาณ SIGABRT ถูกสร้างขึ้นเมื่อมีการเรียกใช้ฟังก์ชัน abort() สัญญาณนี้บ่งชี้ถึงข้อผิดพลาดที่โปรแกรมตรวจพบและรายงานโดยการเรียกใช้ฟังก์ชัน abort()
SIGFPE ข้อยกเว้นทศนิยม เมื่อเกิดข้อผิดพลาดทางคณิตศาสตร์ร้ายแรง สัญญาณ SIGFPE จะถูกสร้างขึ้น
SIGUSR1 และ SIGUSR2 อาจใช้สัญญาณ SIGUSR1 และ SIGUSR2 ได้ตามที่คุณต้องการ เป็นประโยชน์ในการเขียนตัวจัดการสัญญาณสำหรับพวกเขาในโปรแกรมที่รับสัญญาณสำหรับการสื่อสารระหว่างกระบวนการอย่างง่าย

การกระทำเริ่มต้นของสัญญาณ

แต่ละสัญญาณมีการดำเนินการเริ่มต้น อย่างใดอย่างหนึ่งต่อไปนี้:

ภาคเรียน: กระบวนการนี้จะยุติลง
แกนหลัก: กระบวนการนี้จะยุติและสร้างไฟล์ดัมพ์หลัก
อิก: กระบวนการจะละเว้นสัญญาณ
หยุด: กระบวนการจะหยุด
บัญชี: กระบวนการจะดำเนินต่อไปจากการถูกหยุด



การดำเนินการเริ่มต้นอาจเปลี่ยนแปลงได้โดยใช้ฟังก์ชันตัวจัดการ การกระทำเริ่มต้นของสัญญาณบางอย่างไม่สามารถเปลี่ยนแปลงได้ ซิกคิลล์ และ SIGABRT การกระทำเริ่มต้นของสัญญาณไม่สามารถเปลี่ยนแปลงหรือเพิกเฉยได้

การจัดการสัญญาณ

หากกระบวนการรับสัญญาณ กระบวนการมีตัวเลือกการดำเนินการสำหรับสัญญาณประเภทนั้น กระบวนการสามารถละเว้นสัญญาณ สามารถระบุฟังก์ชันตัวจัดการ หรือยอมรับการดำเนินการเริ่มต้นสำหรับสัญญาณชนิดนั้น

  • หากละเว้นการดำเนินการที่ระบุสำหรับสัญญาณ สัญญาณจะถูกยกเลิกทันที
  • โปรแกรมสามารถลงทะเบียนฟังก์ชั่นตัวจัดการโดยใช้ฟังก์ชั่นเช่น สัญญาณ หรือ sigaction . นี้เรียกว่าตัวจัดการจับสัญญาณ
  • ถ้าสัญญาณไม่ได้รับการจัดการหรือละเว้น การดำเนินการเริ่มต้นของสัญญาณจะเกิดขึ้น

เราสามารถจัดการกับสัญญาณโดยใช้ สัญญาณ หรือ sigaction การทำงาน. มาดูกันว่าง่ายที่สุด สัญญาณ() ฟังก์ชั่นใช้สำหรับจัดการสัญญาณ

intสัญญาณ() (intเข้าสู่ระบบ, โมฆะ (*การทำงาน)(int))

NS สัญญาณ() จะเรียก การทำงาน ฟังก์ชันหากกระบวนการได้รับสัญญาณ เข้าสู่ระบบ . NS สัญญาณ() ส่งคืนตัวชี้ไปยังฟังก์ชัน การทำงาน หากสำเร็จหรือส่งกลับข้อผิดพลาดไปที่ errno และ -1 มิฉะนั้น

NS การทำงาน ตัวชี้สามารถมีค่าได้สามค่า:

  1. SIG_DFL : เป็นตัวชี้ไปยังฟังก์ชันเริ่มต้นของระบบ SIG_DFL () , ประกาศใน ชม ไฟล์ส่วนหัว ใช้สำหรับการดำเนินการเริ่มต้นของสัญญาณ
  2. SIG_IGN : เป็นตัวชี้ไปยังฟังก์ชันระบบละเว้น SIG_IGN () ,ประกาศใน ชม ไฟล์ส่วนหัว
  3. ตัวชี้ฟังก์ชันตัวจัดการที่ผู้ใช้กำหนด : ประเภทฟังก์ชันตัวจัดการที่ผู้ใช้กำหนดคือ เป็นโมฆะ(*)(int) หมายถึงประเภทการส่งคืนเป็นโมฆะและอาร์กิวเมนต์ประเภท int หนึ่งรายการ

ตัวอย่างตัวจัดการสัญญาณพื้นฐาน

#รวม
#รวม
#รวม
โมฆะsig_handler(intเข้าสู่ระบบ){

//ประเภทการส่งคืนของฟังก์ชันตัวจัดการควรเป็นโมฆะ
printf ('NSฟังก์ชั่นจัดการภายในNS');
}

intหลัก(){
สัญญาณ(SIGINT,sig_handler); // ลงทะเบียนตัวจัดการสัญญาณ
สำหรับ(intผม=1;;ผม++){ //วนไม่มีสิ้นสุด
printf ('%d : ภายในฟังก์ชันหลักNS',ผม);
นอน(1); // หน่วงเวลา 1 วินาที
}
กลับ 0;
}

ในภาพหน้าจอของผลลัพธ์ของ Example1.c เราจะเห็นว่าในฟังก์ชันหลัก infinite loop กำลังดำเนินการอยู่ เมื่อผู้ใช้พิมพ์ Ctrl+C ฟังก์ชันหลักจะหยุดทำงานและฟังก์ชันตัวจัดการของสัญญาณจะถูกเรียกใช้ หลังจากเสร็จสิ้นฟังก์ชันตัวจัดการ การดำเนินการของฟังก์ชันหลักจะกลับมาทำงานต่อ เมื่อผู้ใช้พิมพ์ Ctrl+ กระบวนการจะหยุด

ละเว้นสัญญาณตัวอย่าง

#รวม
#รวม
#รวม
intหลัก(){
สัญญาณ(SIGINT,SIG_IGN); // ลงทะเบียนตัวจัดการสัญญาณโดยไม่สนใจสัญญาณ

สำหรับ(intผม=1;;ผม++){ //วนไม่มีสิ้นสุด
printf ('%d : ภายในฟังก์ชันหลักNS',ผม);
นอน(1); // หน่วงเวลา 1 วินาที
}
กลับ 0;
}

ฟังก์ชั่นตัวจัดการที่นี่ลงทะเบียนกับ SIG_IGN () ฟังก์ชันละเว้นการทำงานของสัญญาณ ดังนั้น เมื่อผู้ใช้พิมพ์ Ctrl+C SIGINT กำลังสร้างสัญญาณแต่การดำเนินการจะถูกละเว้น

ลงทะเบียนตัวอย่างตัวจัดการสัญญาณ

#รวม
#รวม
#รวม

โมฆะsig_handler(intเข้าสู่ระบบ){
printf ('NSฟังก์ชั่นจัดการภายในNS');
สัญญาณ(SIGINT,SIG_DFL); // ลงทะเบียนตัวจัดการสัญญาณอีกครั้งสำหรับการดำเนินการเริ่มต้น
}

intหลัก(){
สัญญาณ(SIGINT,sig_handler); // ลงทะเบียนตัวจัดการสัญญาณ
สำหรับ(intผม=1;;ผม++){ //วนไม่มีสิ้นสุด
printf ('%d : ภายในฟังก์ชันหลักNS',ผม);
นอน(1); // หน่วงเวลา 1 วินาที
}
กลับ 0;
}

ในภาพหน้าจอของผลลัพธ์ของ Example3.c เราจะเห็นว่าเมื่อผู้ใช้พิมพ์ Ctrl+C เป็นครั้งแรก ฟังก์ชันตัวจัดการจะเรียกทำงาน ในฟังก์ชันตัวจัดการ ตัวจัดการสัญญาณจะลงทะเบียนใหม่เป็น SIG_DFL สำหรับการกระทำเริ่มต้นของสัญญาณ เมื่อผู้ใช้พิมพ์ Ctrl+C เป็นครั้งที่สอง กระบวนการจะสิ้นสุดลงซึ่งเป็นการดำเนินการเริ่มต้นของ SIGINT สัญญาณ.

การส่งสัญญาณ:

กระบวนการยังสามารถส่งสัญญาณไปยังตัวเองหรือไปยังกระบวนการอื่นได้อย่างชัดเจน ฟังก์ชัน rise() และ kill() สามารถใช้ในการส่งสัญญาณได้ ฟังก์ชันทั้งสองถูกประกาศในไฟล์ส่วนหัวของ signal.h

int ยก (intเข้าสู่ระบบ)

ฟังก์ชัน rise() ใช้สำหรับส่งสัญญาณ เข้าสู่ระบบ สู่กระบวนการเรียก (ตัวเอง) คืนค่าศูนย์หากสำเร็จและคืนค่าที่ไม่ใช่ศูนย์หากล้มเหลว

intฆ่า(pid_t pid, intเข้าสู่ระบบ)

ฟังก์ชั่นฆ่าที่ใช้ส่งสัญญาณ เข้าสู่ระบบ ไปยังกระบวนการหรือกลุ่มกระบวนการที่ระบุโดย pid .

ตัวอย่างตัวจัดการสัญญาณ SIGUSR1

#รวม
#รวม

โมฆะsig_handler(intเข้าสู่ระบบ){
printf ('ฟังก์ชั่นจัดการภายในNS');
}

intหลัก(){
สัญญาณ(SIGUSR1,sig_handler); // ลงทะเบียนตัวจัดการสัญญาณ
printf ('ภายในฟังก์ชั่นหลักNS');
ยก (SIGUSR1);
printf ('ภายในฟังก์ชั่นหลักNS');
กลับ 0;
}

ที่นี่ กระบวนการส่งสัญญาณ SIGUSR1 ไปยังตัวเองโดยใช้ฟังก์ชันยก ()

ตัวอย่างโปรแกรม Raise with Kill

#รวม
#รวม
#รวม
โมฆะsig_handler(intเข้าสู่ระบบ){
printf ('ฟังก์ชั่นจัดการภายในNS');
}

intหลัก(){
pid_t pid;
สัญญาณ(SIGUSR1,sig_handler); // ลงทะเบียนตัวจัดการสัญญาณ
printf ('ภายในฟังก์ชั่นหลักNS');
pid=getpid(); // ID กระบวนการของตัวเอง
ฆ่า(pid,SIGUSR1); // ส่ง SIGUSR1 ให้ตัวเอง
printf ('ภายในฟังก์ชั่นหลักNS');
กลับ 0;
}

ที่นี่กระบวนการส่ง SIGUSR1 สัญญาณให้ตัวเองโดยใช้ ฆ่า() การทำงาน. getpid() ใช้เพื่อรับ ID กระบวนการของตัวเอง

ในตัวอย่างต่อไป เราจะมาดูกันว่ากระบวนการของผู้ปกครองและลูกสื่อสารกันอย่างไร (การสื่อสารระหว่างกระบวนการ) โดยใช้ ฆ่า() และฟังก์ชั่นสัญญาณ

ผู้ปกครองเด็กสื่อสารด้วยสัญญาณ

#รวม
#รวม
#รวม
#รวม
โมฆะsig_handler_parent(intเข้าสู่ระบบ){
printf ('ผู้ปกครอง : รับสัญญาณตอบรับจากลูกNS');
}

โมฆะsig_handler_child(intเข้าสู่ระบบ){
printf ('ลูก : รับสัญญาณจากผู้ปกครองNS');
นอน(1);
ฆ่า(getppid(),SIGUSR1);
}

intหลัก(){
pid_t pid;
ถ้า((pid=ส้อม())<0){
printf ('ส้อมล้มเหลวNS');
ทางออก (1);
}
/* กระบวนการย่อย */
อื่น ถ้า(pid==0){
สัญญาณ(SIGUSR1,sig_handler_child); // ลงทะเบียนตัวจัดการสัญญาณ
printf ('ลูก: รอสัญญาณNS');
หยุดชั่วคราว();
}
/* กระบวนการของผู้ปกครอง */
อื่น{
สัญญาณ(SIGUSR1,sig_handler_parent); // ลงทะเบียนตัวจัดการสัญญาณ
นอน(1);
printf ('ผู้ปกครอง: ส่งสัญญาณให้ลูกNS');
ฆ่า(pid,SIGUSR1);
printf ('ผู้ปกครอง: รอการตอบสนองNS');
หยุดชั่วคราว();
}
กลับ 0;
}

ที่นี่, ส้อม() ฟังก์ชันสร้างโปรเซสลูกและคืนค่าศูนย์ไปยังโปรเซสลูกและ ID โปรเซสลูกไปยังโปรเซสพาเรนต์ ดังนั้น pid จึงได้รับการตรวจสอบเพื่อตัดสินกระบวนการระดับบนสุดและระดับย่อย ในกระบวนการหลัก จะถูกสลีปเป็นเวลา 1 วินาที เพื่อให้โปรเซสลูกสามารถลงทะเบียนฟังก์ชันตัวจัดการสัญญาณและรอสัญญาณจากพาเรนต์ หลังจากกระบวนการหลัก 1 วินาที send SIGUSR1 ส่งสัญญาณไปยังกระบวนการลูกและรอสัญญาณตอบรับจากลูก ในกระบวนการลูก อันดับแรกกำลังรอสัญญาณจากพาเรนต์ และเมื่อได้รับสัญญาณ ฟังก์ชันตัวจัดการจะถูกเรียกใช้ จากฟังก์ชันตัวจัดการ โปรเซสลูกจะส่ง another SIGUSR1 สัญญาณถึงผู้ปกครอง ที่นี่ getppid() ฟังก์ชันใช้สำหรับรับ ID กระบวนการหลัก

บทสรุป

Signal ใน Linux เป็นหัวข้อใหญ่ ในบทความนี้ เราได้เห็นวิธีจัดการกับสัญญาณจากพื้นฐานแล้ว ยังได้รับความรู้ว่าสัญญาณสร้างได้อย่างไร กระบวนการสามารถส่งสัญญาณไปยังตัวเองและกระบวนการอื่นๆ ได้อย่างไร สัญญาณสามารถใช้สำหรับการสื่อสารระหว่างกระบวนการได้อย่างไร