โดยทั่วไปจะฆ่ากระบวนการที่เราสร้างสัญญาณเช่นSIGKILL
, SIGTSTP
ฯลฯ
แต่จะทราบได้อย่างไรว่าใครสั่งให้สัญญาณนั้นใครส่งไปยังกระบวนการเฉพาะและโดยทั่วไปสัญญาณทำงานอย่างไร สัญญาณภายในทำงานอย่างไร
โดยทั่วไปจะฆ่ากระบวนการที่เราสร้างสัญญาณเช่นSIGKILL
, SIGTSTP
ฯลฯ
แต่จะทราบได้อย่างไรว่าใครสั่งให้สัญญาณนั้นใครส่งไปยังกระบวนการเฉพาะและโดยทั่วไปสัญญาณทำงานอย่างไร สัญญาณภายในทำงานอย่างไร
คำตอบ:
มุมมอง 50,000 ฟุตคือ:
สัญญาณถูกสร้างขึ้นโดยเคอร์เนลภายใน (ตัวอย่างเช่นSIGSEGV
เมื่อมีการเข้าถึงที่อยู่ที่ไม่ถูกต้องหรือSIGQUIT
เมื่อคุณกดCtrl+ \) หรือโดยโปรแกรมที่ใช้kill
syscall (หรือหลายรายการที่เกี่ยวข้อง)
หากเป็นหนึ่งใน syscalls แสดงว่าเคอร์เนลยืนยันว่ากระบวนการเรียกมีสิทธิ์เพียงพอที่จะส่งสัญญาณ หากไม่มีข้อผิดพลาดจะถูกส่งกลับ (และสัญญาณจะไม่เกิดขึ้น)
หากเป็นสัญญาณพิเศษหนึ่งในสองเคอร์เนลจะทำงานโดยไม่มีเงื่อนไขโดยไม่มีอินพุตจากกระบวนการเป้าหมาย สัญญาณพิเศษสองสัญญาณคือ SIGKILL และ SIGSTOP ทุกสิ่งด้านล่างเกี่ยวกับการกระทำเริ่มต้นการปิดกั้นสัญญาณ ฯลฯ ไม่เกี่ยวข้องกับสิ่งเหล่านี้
ถัดไปเคอร์เนลจะหาว่ามีสัญญาณอะไร:
สำหรับแต่ละกระบวนการมีการดำเนินการที่เกี่ยวข้องกับแต่ละสัญญาณ มีพวงของการเริ่มต้นเป็นและโปรแกรมสามารถตั้งคนที่แตกต่างโดยใช้sigaction
, signal
ฯลฯ เหล่านี้รวมถึงสิ่งที่ชอบ "ไม่สนใจมันสมบูรณ์", "ฆ่ากระบวนการ", "ฆ่ากระบวนการที่มีการถ่ายโอนข้อมูลหลัก", "หยุดกระบวนการ" เป็นต้น
โปรแกรมสามารถปิดการส่งสัญญาณ ("ถูกบล็อก") โดยใช้สัญญาณต่อสัญญาณ จากนั้นสัญญาณจะยังคงค้างอยู่จนกว่าจะไม่ถูกบล็อก
โปรแกรมสามารถร้องขอให้แทนที่เคอร์เนลที่ดำเนินการบางอย่างเองมันส่งสัญญาณไปยังกระบวนการทั้งแบบซิงโครนัส (กับsigwait
, et. al. หรือsignalfd
) หรือแบบอะซิงโครนัส (โดยการขัดจังหวะกระบวนการที่ทำ
มีสัญญาณชุดที่สองที่เรียกว่า "สัญญาณเรียลไทม์" ซึ่งไม่มีความหมายเฉพาะและอนุญาตให้มีสัญญาณหลายรายการที่จะเข้าคิว (สัญญาณคิวปกติเพียงหนึ่งสัญญาณเท่านั้นเมื่อสัญญาณถูกบล็อก) สิ่งเหล่านี้ถูกใช้ในโปรแกรมแบบมัลติเธรดเพื่อให้เธรดสามารถสื่อสารกันได้ ตัวอย่างจำนวนมากถูกใช้ในการใช้เธรด POSIX ของ glibc ตัวอย่างเช่น พวกเขายังสามารถใช้ในการสื่อสารระหว่างกระบวนการต่าง ๆ (ตัวอย่างเช่นคุณสามารถใช้สัญญาณเรียลไทม์จำนวนมากเพื่อให้โปรแกรม fooctl ส่งข้อความไปยัง foo daemon)
สำหรับมุมมองที่ไม่ใช่ 50,000 ลองman 7 signal
และเอกสารประกอบเคอร์เนล internals (หรือแหล่งที่มา)
การปรับใช้สัญญาณมีความซับซ้อนมากและมีความเฉพาะเจาะจงของเคอร์เนล ในคำอื่น ๆ เมล็ดที่แตกต่างกันจะใช้สัญญาณต่างกัน คำอธิบายที่ง่ายขึ้นมีดังนี้:
CPU ขึ้นอยู่กับค่าลงทะเบียนพิเศษมีที่อยู่ในหน่วยความจำซึ่งคาดว่าจะพบ "ตารางตัวอธิบายขัดจังหวะ" ซึ่งจริงๆแล้วเป็นตารางเวกเตอร์ มีเวกเตอร์เดียวสำหรับทุกข้อยกเว้นที่เป็นไปได้เช่นการหารด้วยศูนย์หรือกับดักเช่น INT 3 (ดีบั๊ก) เมื่อ CPU พบข้อยกเว้นมันจะบันทึกสถานะและตัวชี้คำสั่งปัจจุบันบนสแต็กจากนั้นข้ามไปยังที่อยู่ที่ระบุโดยเวกเตอร์ที่เกี่ยวข้อง ใน Linux เวกเตอร์นี้ชี้ไปที่เคอร์เนลเสมอซึ่งมีตัวจัดการข้อยกเว้น CPU เสร็จสิ้นแล้วและเคอร์เนล Linux จะเข้าแทนที่
โปรดทราบว่าคุณยังสามารถทริกเกอร์ข้อยกเว้นจากซอฟต์แวร์ ตัวอย่างเช่นผู้ใช้กดCTRL- Cจากนั้นการโทรนี้จะไปที่เคอร์เนลซึ่งเรียกตัวจัดการข้อยกเว้นของตัวเอง โดยทั่วไปมีวิธีที่แตกต่างกันในการเข้าถึงตัวจัดการ แต่ไม่ว่าจะเกิดอะไรขึ้นกับพื้นฐาน: บริบทจะถูกบันทึกไว้ในสแต็กและตัวจัดการข้อยกเว้นของเคอร์เนลจะถูกข้ามไป
ตัวจัดการข้อยกเว้นจะตัดสินใจว่าเธรดใดควรรับสัญญาณ หากสิ่งที่ต้องการแบ่งเป็นศูนย์เกิดขึ้นมันเป็นเรื่องง่าย: เธรดที่ทำให้เกิดข้อยกเว้นได้รับสัญญาณ แต่สำหรับสัญญาณประเภทอื่นการตัดสินใจอาจซับซ้อนมากและในบางกรณีที่ผิดปกติอาจมีเธรดสุ่มมากหรือน้อยก็ได้ รับสัญญาณ
ในการส่งสัญญาณสิ่งที่เคอร์เนลทำคือตั้งค่าที่ระบุประเภทของสัญญาณSIGHUP
หรืออะไรก็ตาม นี่เป็นเพียงจำนวนเต็ม ทุกกระบวนการมีพื้นที่หน่วยความจำ "รอสัญญาณ" ที่เก็บค่านี้ จากนั้นเคอร์เนลจะสร้างโครงสร้างข้อมูลพร้อมกับข้อมูลสัญญาณ โครงสร้างนี้รวมถึงสัญญาณ "การจัดการ" ซึ่งอาจเป็นค่าเริ่มต้นละเว้นหรือจัดการ do_signal()
เคอร์เนลแล้วเรียกฟังก์ชั่นของตัวเอง ระยะต่อไปจะเริ่มขึ้น
do_signal()
ครั้งแรกที่ตัดสินใจว่ามันจะจัดการสัญญาณ ตัวอย่างเช่นถ้าเป็นการฆ่าก็do_signal()
แค่ฆ่ากระบวนการจบเรื่อง มิฉะนั้นจะดูที่การจำหน่าย หากการจัดการเป็นค่าเริ่มต้นแล้วdo_signal()
จัดการสัญญาณตามนโยบายเริ่มต้นที่ขึ้นอยู่กับสัญญาณ หากการจัดการมีการจัดการก็หมายความว่ามีฟังก์ชั่นในโปรแกรมผู้ใช้ที่ถูกออกแบบมาเพื่อจัดการกับสัญญาณที่เป็นปัญหาและตัวชี้ไปยังฟังก์ชั่นนี้จะอยู่ในโครงสร้างข้อมูลดังกล่าวข้างต้น ในกรณีนี้ do_signal () เรียกใช้ฟังก์ชันเคอร์เนลอื่นhandle_signal()
ซึ่งจะผ่านกระบวนการสลับกลับไปเป็นโหมดผู้ใช้และเรียกใช้ฟังก์ชันนี้ รายละเอียดของการแฮนด์ออฟนี้มีความซับซ้อนมาก signal.h
รหัสในโปรแกรมของคุณนี้มักจะมีการเชื่อมโยงโดยอัตโนมัติในโปรแกรมของคุณเมื่อคุณใช้ฟังก์ชั่นใน
โดยการตรวจสอบค่าสัญญาณที่รอดำเนินการอย่างเหมาะสมเคอร์เนลสามารถกำหนดได้ว่ากระบวนการจัดการสัญญาณทั้งหมดหรือไม่และจะดำเนินการตามความเหมาะสมหากไม่เป็นเช่นนั้นซึ่งอาจทำให้กระบวนการเข้าสู่โหมดสลีปหรือฆ่าหรือดำเนินการอื่น ๆ ขึ้นอยู่กับสัญญาณ
แม้ว่าคำถามนี้จะได้รับคำตอบให้ฉันโพสต์รายละเอียดไหลของเหตุการณ์ใน Linux kernel
สิ่งนี้จะถูกคัดลอกมาจากกระทู้ Linux: สัญญาณ Linux - ภายใน
ที่บล็อก“ โพสต์ Linux” ที่ sklinuxblog.blogspot.in
เริ่มต้นด้วยการเขียนโปรแกรม C พื้นที่ผู้ใช้สัญญาณอย่างง่าย:
#include<signal.h>
#include<stdio.h>
/* Handler function */
void handler(int sig) {
printf("Receive signal: %u\n", sig);
};
int main(void) {
struct sigaction sig_a;
/* Initialize the signal handler structure */
sig_a.sa_handler = handler;
sigemptyset(&sig_a.sa_mask);
sig_a.sa_flags = 0;
/* Assign a new handler function to the SIGINT signal */
sigaction(SIGINT, &sig_a, NULL);
/* Block and wait until a signal arrives */
while (1) {
sigsuspend(&sig_a.sa_mask);
printf("loop\n");
}
return 0;
};
รหัสนี้กำหนดตัวจัดการใหม่สำหรับสัญญาณ SIGINT SIGINT สามารถถูกส่งไปยังกระบวนการที่ทำงานโดยใช้Ctrl+ Cชุดคีย์ เมื่อกดCtrl+ Cสัญญาณอะซิงโครนัส SIGINT จะถูกส่งไปยังงาน นอกจากนี้ยังเทียบเท่ากับการส่งkill -INT <pid>
คำสั่งในเทอร์มินัลอื่น
หากคุณทำkill -l
(นั่นคือตัวพิมพ์เล็กL
ซึ่งหมายถึง "รายการ") คุณจะได้รู้สัญญาณต่าง ๆ ที่สามารถส่งไปยังกระบวนการที่กำลังทำงานอยู่
[root@linux ~]# kill -l
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) SIGURG 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
การผสมคีย์ต่อไปนี้สามารถใช้ในการส่งสัญญาณโดยเฉพาะ:
หากคุณรวบรวมและเรียกใช้โปรแกรม C ข้างต้นแล้วคุณจะได้รับผลลัพธ์ต่อไปนี้:
[root@linux signal]# ./a.out
Receive signal: 2
loop
Receive signal: 2
loop
^CReceive signal: 2
loop
แม้จะมีCtrl+ Cหรือkill -2 <pid>
กระบวนการจะไม่ยุติ แต่มันจะดำเนินการจัดการสัญญาณและกลับมา
หากเราเห็น internals ของสัญญาณที่ส่งไปยังกระบวนการและใส่ Jprobe กับ dump_stack ที่__send_signal
ฟังก์ชันเราจะเห็นการติดตามการโทร:
May 5 16:18:37 linux kernel: dump_stack+0x19/0x1b
May 5 16:18:37 linux kernel: my_handler+0x29/0x30 (probe)
May 5 16:18:37 linux kernel: complete_signal+0x205/0x250
May 5 16:18:37 linux kernel: __send_signal+0x194/0x4b0
May 5 16:18:37 linux kernel: send_signal+0x3e/0x80
May 5 16:18:37 linux kernel: do_send_sig_info+0x52/0xa0
May 5 16:18:37 linux kernel: group_send_sig_info+0x46/0x50
May 5 16:18:37 linux kernel: __kill_pgrp_info+0x4d/0x80
May 5 16:18:37 linux kernel: kill_pgrp+0x35/0x50
May 5 16:18:37 linux kernel: n_tty_receive_char+0x42b/0xe30
May 5 16:18:37 linux kernel: ? ftrace_ops_list_func+0x106/0x120
May 5 16:18:37 linux kernel: n_tty_receive_buf+0x1ac/0x470
May 5 16:18:37 linux kernel: flush_to_ldisc+0x109/0x160
May 5 16:18:37 linux kernel: process_one_work+0x17b/0x460
May 5 16:18:37 linux kernel: worker_thread+0x11b/0x400
May 5 16:18:37 linux kernel: rescuer_thread+0x400/0x400
May 5 16:18:37 linux kernel: kthread+0xcf/0xe0
May 5 16:18:37 linux kernel: kthread_create_on_node+0x140/0x140
May 5 16:18:37 linux kernel: ret_from_fork+0x7c/0xb0
May 5 16:18:37 linux kernel: ? kthread_create_on_node+0x140/0x140
ดังนั้นฟังก์ชั่นหลักเรียกร้องให้มีการส่งสัญญาณเช่น:
First shell send the Ctrl+C signal using n_tty_receive_char
n_tty_receive_char()
isig()
kill_pgrp()
__kill_pgrp_info()
group_send_sig_info() -- for each PID in group call this function
do_send_sig_info()
send_signal()
__send_signal() -- allocates a signal structure and add to task pending signals
complete_signal()
signal_wake_up()
signal_wake_up_state() -- sets TIF_SIGPENDING in the task_struct flags. Then it wake up the thread to which signal was delivered.
ตอนนี้ทุกอย่างได้รับการตั้งค่าและการเปลี่ยนแปลงที่จำเป็นจะเกิดขึ้นกับtask_struct
กระบวนการ
สัญญาณจะถูกตรวจสอบ / จัดการโดยกระบวนการเมื่อมันกลับมาจากการเรียกของระบบหรือถ้าการส่งคืนจากการขัดจังหวะเสร็จสิ้น entry_64.S
ผลตอบแทนจากการเรียกระบบมีอยู่ในไฟล์
ฟังก์ชั่นการทำงาน int_signal ถูกเรียกจากที่เรียกฟังก์ชั่นentry_64.S
do_notify_resume()
do_notify_resume()
ขอตรวจสอบการทำงาน ฟังก์ชันนี้ตรวจสอบว่าเรามีการTIF_SIGPENDING
ตั้งค่าสถานะในtask_struct
:
/* deal with pending signal delivery */
if (thread_info_flags & _TIF_SIGPENDING)
do_signal(regs);
do_signal calls handle_signal to call the signal specific handler
Signals are actually run in user mode in function:
__setup_rt_frame -- this sets up the instruction pointer to handler: regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
“ช้า” syscalls เช่นการปิดกั้นการอ่าน / เขียนใส่เข้าไปในกระบวนการรอรัฐ:
หรือTASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE
งานในสถานะTASK_INTERRUPTIBLE
จะถูกเปลี่ยนเป็นTASK_RUNNING
สถานะโดยสัญญาณ TASK_RUNNING
หมายถึงกระบวนการสามารถกำหนด
หากดำเนินการแล้วตัวจัดการสัญญาณของมันจะถูกเรียกใช้ก่อนที่จะเสร็จ syscall "ช้า" syscall
ไม่สมบูรณ์โดยค่าเริ่มต้น
หากSA_RESTART
ตั้งค่าสถานะsyscall
จะเริ่มต้นใหม่หลังจากตัวจัดการสัญญาณเสร็จสิ้น
kill
คำสั่งซึ่งเป็นเชลล์ในตัว) (2c) แม้ว่าเซมิโคลอนหลังจากปิด}
ฟังก์ชั่นจะไม่พูดอย่างผิดพลาด แต่ก็ไม่จำเป็นและไม่แหกคอกอย่างมาก (3) แม้ว่าทุกอย่างจะถูกต้อง แต่ก็ไม่ใช่คำตอบที่ดีสำหรับคำถาม (3a) คำถามในขณะที่ค่อนข้างไม่ชัดเจนดูเหมือนว่าจะมุ่งเน้นไปที่วิธีที่นักแสดง (ผู้ใช้และกระบวนการ) เริ่มต้นสัญญาณ (เช่นส่ง ) … (ต่อ)