리눅스 커널 코드 리뷰를 많이 했는데요.
리눅스 시스템 프로그램이 커널과 어떻게 연동되는지 한 가지 점검해볼께요.
유저 공간에서 signal을 설정하는 코드를 많이 볼 수 있습니다.
아래는 SIGINT란 시그널이 전달되었을 때 linux_sig()란 함수가 호출되는 간단한 코드입니다.
그럼 아래 시그널 콜백 함수가 어떻게 실행이 될까요?
#include <stdio.h>
#define SIGINT (2)
typedef void (*handler_t)(int);
handler_t signal( int, handler_t );
handler_t old;
void linux_sig( int signo )
{
printf("linux_sig(%d)\n", signo );
signal( SIGINT, old );
}
int main()
{
old = signal( SIGINT, linux_sig);
while(1)
;
return 0;
}
- 설정 코드
signal 관련해서 중요한 task descriptor의 멤버 변수가 있는데요.
각각 멤버에 대해서 살펴볼께요.
struct task_struct {
volatile long state;
void *stack;
// ..생략...
struct signal_struct *signal;
struct sighand_struct *sighand;
sigset_t blocked;
sigset_t saved_sigmask;
struct sigpending pending;
sighand
struct task_struct.sighand.action[0--64].sa_handler에 sig handler 주소를 설정합니다.
위 예제의 경우 task_struct.sighand.action[SIGINT].sa_handler = my_sig 이렇게 되는거죠.
타입은 struct sighand_struct* 이구요. 여기서 중요한 멤버가 action인데요. 64만큼의 배열로 되어 있어요.
조금 더 자세히 짚어보면 struct task_struct.sighand.action[0--64]에 위치하고 있어요.
struct task_struct.sighand.action의 각각 배열들은 각 signal에 대한 sig handle 정보를 담고 있어요.
예를 들면, 아래와 같죠.
struct task_struct.sighand.action[SIGHUP] // #define SIGHUP 1
struct task_struct.sighand.action[SIGINT] // #define SIGINT 2
struct task_struct.sighand.action[SIGQUIT] // #define SIGQUIT 3
.. 생략..
struct task_struct.sighand.action[SIGTRAP] // #define SIGTRAP 5
struct task_struct.sighand.action[SIGABRT] // #define SIGABRT 6
.. 생략..
struct task_struct.sighand.action[ SIGKILL] // #define SIGKILL 9
위에서 만든 my_sig 프로세스의 task_descriptor가 d7e09840이면,
crash tool로 아래와 같이 각각 멤버 변수를 확인할 수 있어요.
crash> struct task_struct.sighand d7e09840
sighand = 0xdab7bec0
crash> struct sighand_struct 0xdab7bec0 -px
struct sighand_struct {
count = {
counter = 0x86
},
action = {{
sa = {
sa_handler = 0x0,
sa_flags = 0x0,
sa_restorer = 0x0,
sa_mask = {
sig = {0x0, 0x0}
}
}
}, {
sa = { //<<-- SIGHUP
sa_handler = 0x0,
sa_flags = 0x0,
sa_restorer = 0x0,
sa_mask = {
sig = {0x0, 0x0}
}
}
}, {
sa = { //<<--SIGINT
sa_handler = 0xa71eacad, //<<-- linux_sig의 가상 메모리(유저 공간) 주소
sa_flags = 0x1c000004,
sa_restorer = 0xa723ea08,
sa_mask = {
sig = {0xfffbfeff, 0x0}
}
}
}, {
sa = { //<<-- SIGQUIT
sa_handler = 0x0,
sa_flags = 0x0,
sa_restorer = 0x0,
sa_mask = {
sig = {0x0, 0x0}
}
}
signal 관련해서 struct sighand_struct 구조체는 아래와 같아요.
crash> struct sighand_struct
struct sighand_struct {
atomic_t count;
struct k_sigaction action[64];
spinlock_t siglock;
wait_queue_head_t signalfd_wqh;
}
crash> struct k_sigaction
struct k_sigaction {
struct sigaction sa;
pending
해당 프로세스에 대해 시그널을 설명하면, struct task_struct.pending.signal.sig란 Vector Hash Table에 해당 시그널 필드를 0x1로 설정하죠.
위 예제의 경우 SIGIN 필드(0x2)가 0x1로 설정 되죠.
sig |0|0|0|0|1|0|0|
crash> struct task_struct.pending d7e09840
pending = {
list = {
next = 0xd7e09dd8,
prev = 0xd7e09dd8
},
signal = {
sig = {2, 0}
}
}
get_signal-> dequeue_signal-> __dequeue_signal 함수 흐름으로 아래 함수가 호출되는데요.
struct task_struct.pending 필드를 확인한 다음에 해당 프로세스에 signal을 전달합니다.
int next_signal(struct sigpending *pending, sigset_t *mask)
{
unsigned long i, *s, *m, x;
int sig = 0;
s = pending->signal.sig; //<<--
m = mask->sig;
... 생략 ...
return sig;
}
static int __dequeue_signal(struct sigpending *pending, sigset_t *mask,
siginfo_t *info, bool *resched_timer)
{
int sig = next_signal(pending, mask);
if (sig) {
if (current->notifier) {
if (sigismember(current->notifier_mask, sig)) {
if (!(current->notifier)(current->notifier_data)) {
clear_thread_flag(TIF_SIGPENDING);
return 0;
}
}
}
collect_signal(sig, pending, info, resched_timer);
}
pending 시그널 정보를 가져오는 매크로는 아래와 같네요.
#define PENDING(p,b) has_pending_signals(&(p)->signal, (b))
Reference(프로세스 관리)
#Reference(리눅스 커널 런큐 & 프로세스)
최근 덧글