Skip to content

Latest commit

 

History

History
1029 lines (776 loc) · 37.3 KB

File metadata and controls

1029 lines (776 loc) · 37.3 KB

Interrupt

  • What is Interrupt
  • interrupt number
  • handling interrupt
  • interrupt service routine: ISR
    • ISR1, ISR2
  • OS = initialization code + collection of ISR's

An operating system is a collection of service routines. The service routine can be executed by a request from an application (system call interrupt).
Or it runs automatically when there is a serious error while running an application (exception interrupt) or when there is an external hardware event that the operating system has to handle (hardware interrupt).
The service routines are called ISRs (Interrupt Service Routines).

external event interrupt number ISR (ISR1 => ISR2)
An application calls
read(...)
int 128
(syscall num 3)
system_call() => sys_read()
An application calls
write(...)
int 128
(syscall num 4)
system_call() => sys_write()
timer ticks int 32 interrupt[0] => timer_interrupt()
key stroke int 33 interrupt[1] => atkbd_interrupt()
An application runs
x=x/0;
int 0 divide_error() => do_divide_error()
page fault while an application run int 14 page_fault() => do_page_fault()

ISR1s are all located in arch/x86/kernel/entry_32.S.
ISR2s are located in various locations of the kernel.

When an interrupt, INT x, happens, the cpu stores the current cs, eip, flag register into stack and jumps to ISR1 for INT x.
The ISR1 locations are written in IDT (Interrupt Descriptor Table), and the cpu jumpts to the location written in IDT[x].
ISR1 knows the location of ISR2.
It knows the location of ISR2 because it is hard-coded (exception interrupt case), or is written in irq_desc table (hardware interrupt case) or is written in syscall_table (system call interrupt case).

1. Interrupt classification and Interrupt number

  • There are two kinds of interrupt:
    • software interrupt
    • and hardware interrupt.

  • Software interrupt is generated by a program when it makes a serious error (e.g. dividing by zero) or runs a special instruction (e.g. INT, SYSENTER, SYSCALL).
    • The former is called exception and the latter system call.
  • Hardware interrupt is generated by peripheral devices connected to CPU.
    • Key press is one example of hardware interrupt.

  • All interrupts have a unique number defined by the operating system.
  • Operating system is a collection of routines that handle interrupts.

Hardware interrupts have been assigned following interrupt numbers in Linux.

device interrupt number irq number
timer 32 0
keyboard 33 1
PIC cascading 34 2
second serial port 35 3
first serial port 36 4
floppy disk 38 6
system clock 40 8
network interface 42 10
usb port, sound card 43 11
ps/2 mouse 44 12
math coprocessor 45 13
eide disk, first chain 46 14
eide disk, second chain 47 15

Exceptions have been assigned following interrupt numbers.

exception interrupt number
divide-by-zero error 0
debug 1
NMI 2
breakpoint 3
overflow 4
bounds check 5
invalid opcode 6
device not available 7
double fault 8
coprocessor segment overrun 9
invalid TSS 10
segment not present 11
stack segment fault 12
general protection 13
page fault 14
intel-reserved 15
floating point error 16
alignment check 17
machine check 18
simd floating point 19

Finally, system calls in Linux are all assigned the same interrupt number, 128 (0x80). To differentiate between different system calls, a unique system call number has been given to each system call. For the full table, look at arch/x86/kernel/syscall_table_32.S.

system calls interrupt number system call number
exit 128 1
fork 128 2
read 128 3
write 128 4
open 128 5
close 128 6
...... ...... ......

2. How interrupts are detected?

Interrupts are detected by CPU. Exceptions are detected when the corresponding error happens. System calls are detected when the program executes INT 128 instruction. Hardware interrupts are detected when the corresponding devices are affected. Hardware interrupts need more detailed explanation.

The above picture shows how hardware interrupts are detected by the CPU. All hardware devices are connected to 8259A interrupt controller through IRQ lines. Timer is connected through IRQ0 line, keyboard is connected through IRQ1 line, and so on. When an event happens in one of these devices, the corresponding IRQ line is activated, and 8259A signals CPU about this event along with the corresponding interrupt number for this IRQ line. The interrupt number is computed as (IRQ line number + 32) in Linux.

3. How interrupts are handled

Interrupts are first handled by the CPU, and then the operating system takes care of the rest of things.

3.1 cpu part

  • When an interrupt happens, the CPU executes the corresponding INT instruction.
  • For example, if the user presses a key (which corresponds to INT 33), the CPU executes INT 33.
  • Executing INT x instruction is two steps:
    • save current EFLAG, CS,EIP registers in the stack
    • jump to the location specified in IDT[x]

  • IDT(Interrupt Descriptor Table) is a table containing the address of ISR’s(Interrupt Service Routines).
  • More general name for IDT is Interrupt Vector Table. If IDT[32] indicates address 0x10200, the ISR for timer interrupt is located at address 0x10200, which means whenever the timer ticks, the cpu jumps to address 0x10200.
  • If IDT[33] indicates address 0x10300, the ISR for keyboard is located at 0x10300.
  • Whenever the user hits some key, the cpu will jump to 0x10300 and start to execute whatever program stored there.

  • It is the responsibility of the operating system to provide the IDT and fill in proper address for each interrupt.
  • Linux writes IDT in arch/x86/kernel/traps_32.c/trap_init() (for exception interrupts and system call interrupt) and in arch/x86/kernel/i8259_32.c/init_IRQ() (for hardware interrupts).
  • Also the cpu knows the location of IDT by its IDTR register.
  • Therefore, it is again the responsibility of operating system to write the location of the IDT in IDTR register.
  • Each entry in IDT is 8 byte.
  • The variable name of IDT table in Linux is idt_table.

3.2 OS part

  • Once the cpu jumps to the corresponding ISR, OS takes the control since ISR belongs to the operating system.
  • All ISR’s (I call ISR1) consist of three steps:
    • save the rest of registers (eflag, cs, eip are already saved by cpu)
    • call actual interrupt handler (I call ISR2)
    • recover the saved registers and go back to the interrupted location
  • Linux writes ISR1's in IDT by calling set_intr_gate() for hardware interrupts in arch/x86/kernel/i8259_32.c/native_init_IRQ(), and by calling set_trap_gate() for most of the exceptions and set_system_gate() for system call interrupts in arch/x86/kernel/traps_32.c/trap_init().
  • Device drivers write their ISR2's for hardware interrupts in irq_desc[] table by calling request_irq().
  • ISR2's for exceptions are directly called in the corresponding ISR1's, the name always being do_(ISR1 name). - ISR2's for the system calls are hard-coded in sys_call_table[] in arch/x86/kernel/syscall_table_32.S.
  • ISR1s are defined in arch/x86/kernel/entry_32.S, and ISR2s are defined in various places.

Interrupt numbers and their ISR1 and ISR2 list.

interrupt number ISR1 ISR2
0 divide_error do_divide_error
1 debug do_debug
...............
32 interrupt[0] timer_interrupt
33 interrupt[1] atkbd_interrupt
................
128 (syscall num 1) system_call sys_exit
128 (syscall num 2) system_call sys_fork
.......................

4. Creating a new system call and using it

Creating a new system call : 2 steps

  • find empty sys_call_table entry (sys_ni_syscall) : x
    1. write new system call name: my_syscall
    1. define my_syscall (in appropriate file such as fs/read_write.c)
    asmlinkage void my_syscall(){
        printk("hello from my_syscall\n");
    }
  • recompile and reboot

Using ex1.c:

void main(){
  syscall(x);
}

./ex1

==> syscall(x)
==> mov eax, x
       int   0x80
==> system_call
==> my_syscall

5. Exercise

1) Following events will cause interrupts in the system. What interrupt number will be assigned to each event? For system call interrupt, also give the system call number.

  • A packet has arrived
    • network interface : 42
  • An application program calls scanf()
    • system_call() => sys_read()(3) : 128
  • A key is pressed
    • interrupt[1] : 33
  • An application causes a divide-by-zero error
    • Exception interrupt : 0
  • An application program calls printf()
    • system_call() => sys_read()(3) :128
  • An application causes a page-fault error
    • page_fault() => do_page_fault() :14

2) Change drivers/input/keyboard/atkbd.c as follows.

$ vi drivers/input/keyboard/atkbd.c

drivers/input/keyboard/atkbd.c :

static irqreturn_t atkbd_interrupt(....){
   return IRQ_HANDLED;  // Add this at the first line
   .............
}

Recompile the kernel and reboot with it.

$ make bzImage
$ cp arch/x86/boot/bzImage /boot/bzImage
$ reboot

What happens and why does this happen? Show the sequence of events that happen when you hit a key in a normal Linux kernel (as detail as possible): hit a key => keyboard controller sends a signal through IRQ line 1 => ......etc. Now with the changed Linux kernel show which step in this sequence has been modified and prevents the kernel to display the pressed key in the monitor.

부팅이 끝나면 로그인 입력이 출력되지만, Keyboard action이 먹히지 않는다.

원래 코드는interrupt가 발생 후, 키보드 입력과정을 처리한 뒤 IRQ_HANDLED를 리턴하는데, 이 과정들을 거치지 않고 바로 리턴하도록 했기 때문에, 어떤 문자를 입력해도 실행이 되지 않는다.

3) Change the kernel such that it prints "x pressed" for each key pressing, where x is the scan code of the key. After you change the kernel and reboot it, do followings to see the effect of your changing.

$ vi drivers/input/keyboard/atkbd.c

codeprintk로 출력할 수 있도록 printk("%x pressed\n", code);를 추가하였다.

drivers/input/keyboard/atkbd.c :

이후, 컴파일하고 재부팅하여 새로운 리눅스 커널을 적용하였다.

$ cat /proc/sys/kernel/printk
1  4  1  7

위는 현재의 콘솔 로그 레벨, 기본 로그 레벨, 최소 로그 레렐, 최대 로그 레벨을 나타낸다. 현재는 1로 기본 레벨보다 낮기 때문에 printk()로 출력되는 문자들이 화면에 나타나지 않는다.

$ echo 8 > /proc/sys/kernel/printk

위 명령으로 현재 콘솔 로그 레벨을 8로 바꾸면 아래와 같이 레벨이 바뀐다.

$ cat /proc/sys/kernel/printk
8   4   1   7

현재는 레벨이 8이기 때문에 printk로 출력되는 문자들이 화면에 보인다. 위와 같이 입력되는 키 코드가 화면에 보인다.

$ echo 1 > /proc/sys/kernel/printk

printk 출력을 보이지 않게 하려면 현재 로그 레벨을 1로 되돌리면 된다.

4) Change the kernel such that it displays the next character in the keyboard scancode table. For example, when you type "root", the monitor would display "tppy". How can you log in as root with this kernel?

$ vi drivers/input/keyboard/atkbd.c

키보드 입력이 들어오면, 실제 입력한 글자의 다음 글자를 입력한 것으로 처리하도록 unsigned int code = data;unsigned int code = data+1;로 수정하였다.

drivers/input/keyboard/atkbd.c :

이후, 컴파일하고 재부팅하여 새로운 리눅스 커널을 적용하였다.

재부팅하고 로그인을 하기 위해 "root"를 입력하면 키보드에서 한 글자씩 밀린 "tppy"가 출력된다.

5) Define a function mydelay in init/main.c which whenever called will stop the booting process until you hit 's'. Call this function after do_basic_setup() function call in kernel_init() in order to make the kernel stop and wait for 's' during the booting process. You need to modify atkbd.c such that it changes exit_mydelay to 1 when the user presses 's'.


init/main.c :

........
int exit_mydelay;    // define a global variable
void mydelay(char *str){
   printk(str);
   printk("enter s to continue\n");
   exit_mydelay=0;  // init to zero
   for(;;){  // and wait here until the user press 's'
      msleep(1); // sleep 1 micro-second so that keyboard interrupt ISR
                 // can do its job
      if (exit_mydelay==1) break; // if the user press 's', break
   }
}
void kernel_init(){
    ...............
    do_basic_setup();
    mydelay("after do basic setup in kernel_init\n"); // wait here
    .........
}

drivers/input/keyboard/atkbd.c :

.........
extern int exit_mydelay;  // declare as extern since it is defined in main.c
static irqreturn_t atkbd_interrupt(....){
    .............
    // detect 's' key pressed and change exit_mydelay
    if (code == 31) {
        printk("s pressed\n");
        exit_mydelay = 1;
    }
    .............
}

이후, 컴파일하고 재부팅하여 새로운 리눅스 커널을 적용하였다.

부팅 시 "enter s to continue"라는 메세지와 함께 사용자의 입력을 기다리고 있다.

's'를 입력하면 부팅이 이어서 진행된다.

5-1) Add mydelay before do_basic_setup(). What happens and why?


init/main.c :

void kernel_init(){
    ...............
    mydelay("before do basic setup in kernel_init\n"); // wait here
    do_basic_setup();
    mydelay("after do basic setup in kernel_init\n"); // wait here
    .........
}

이후, 컴파일하고 재부팅하여 새로운 리눅스 커널을 적용하였다.

부팅하면 mydelay() 함수가 실행되어 아래와 같은 메세지를 볼 수 있다.

하지만, do_basic_setup() 함수가 실행되기 전에는 키보드 입력이 불가하여 's'를 입력할 수 없었고, 이후의 부팅 과정을 이어서 실행할 수 없었다.

6) Which function call in atkbd_interrupt() actually displays the pressed key in the monitor?


drivers/input/keyboard/atkbd.c :

키보드로 입력한 key를 찾는 Prob 3)에서 drivers/input/keyboard/atkbd.cprintk("%x pressed\n", code);를 추가한 것으로 미루어보아,
키를 화면에 출력하는 함수는 code 변수를 인자로 가져야할 것이라고 유추할 수 있다.

그래서 대입, 비교, 조건문에 쓰인 code 변수를 제외하고 관련된 함수들을 아래와 같이 모두 찾아보았다.

또한 code를 이용하여 keycode라는 변수를 만들어 이용하기 때문에 keycode 변수를 인자로 갖는 함수도 찾아보았다.

input_event 함수와 input_report_key 함수를 찾아보면 해답이 나올 것으로 보인다.

include/linux/input.h :

input_report_key 함수 역시 input_event 함수를 활용한다는 것을 알 수 있다.

drivers/input/input.c:

input_handle_event가 event를 처리하는 함수라 추측하고 해당 파일 내에서 함수의 정의를 찾아보았다.

해당 함수의 마지막줄에서 input_pass_event 함수에 code 변수를 인자로 넘기며 호출한다.

input_pass_event 함수의 정의를 보면, 해당 함수에서 handler 구조체의 event를 호출하는 것으로 보아, atkbd_interrupt() 내에서 input_event가 키를 모니터에 띄운다고 예상할 수 있다.

6-1) What are the interrupt numbers for divide-by-zero exception, keyboard interrupt, and "read" system call? Where is ISR1 and ISR2 for each of them (write the exact code location)? Show their code, too.

Interrupt Number ISR1 ISR2
divide-by-zero exception 0 divide_error do_divide_error
keyboard interrupt 33 interrupt[1] atkbd_interrupt
read system call 128 system_call sys_read
location
ISR1 arch/x86/kernel/entry_32.S
ISR2(do_divide_error) arch/x86/kernel/traps_32.c
ISR2(atkbd_interrupt) drivers/input/keyboard/atkbd.c
ISR2(sys_read) fs/read_write.c
ISR1

divide-by-zero exception의 ISR1(divide_error)를 확인해보자.
arch/x86/kernel/entry_32.S :


keyboard interrupt의 ISR1(interrupt[1])를 확인해보자.
arch/x86/kernel/entry_32.S :


read system call의 ISR1(system_call)를 확인해보자.
arch/x86/kernel/entry_32.S :

...중간생략...

ISR2

keyboard interrupt의 ISR2(atkbd_interrupt) 실제 코드
arch/x86/kernel/traps_32.c :

divide-by-zero exception의 ISR2(do_divide_error)가 0번인 것을 확인할 수 있다.


keyboard interrupt의 ISR2(atkbd_interrupt) 실제 코드
drivers/input/keyboard/atkbd.c :


read system call의 ISR2(sys_read)의 실제 코드
fs/read_write.c :

7) sys_call_table[] is in arch/x86/kernel/syscall_table_32.S. How many system calls does Linux 2.6 support?
What are the system call numbers for exit, fork, execve, wait4, read, write, and mkdir? Find system call numbers for sys_ni_syscall, which is defined at kernel/sys_ni.c. What is the role of sys_ni_syscall?

$ vi arch/x86/kernel/syscall_table_32.S

arch/x86/kernel/syscall_table_32.S : system calls들이 0부터 시작하여 326까지 있으므로

Linux 2.6은 327개의 system call을 지원한다.

첫번째 스크린샷을 참고하면, exit는 1번, fork는 2번, execve는 11번, read는 3번, write는 4번이다.

또한 wait4는 114번이고,

mkdir는 39번임을 알 수 있다.

sys_ni_call은 시스템 콜 번호로 17, 31, 32번 등 여러 번호가 있는데 kernel/sys_ni.c로 가서 파일을 열어보면 아래와 같다.

kernel/sys_ni.c :

sys_ni_syscall는 구현되지 않은 시스템 콜을 가리키는 함수이며 -ENOSYS을 반환한다. ENOSYS는 구현되지 않은 함수를 사용할 때 발생하는 오류 코드이다.

8) Change the kernel such that it prints "length 17 string found" for each printf(s) when the length of s is 17. Run a program that contains a printf() statement to see the effect. printf(s) calls write(1, s, strlen(s)) system call which in turn runs

printf(s)는 내부적으로 write(1, s, strlen(s))를 호출한다. 따라서 printf를 호출할 때 같이 실행되는 코드를 삽입하기 위해서는 write 함수가 호출되는 시점을 알아야 한다.

fs/read_write.c :

write 함수는 sys_write를 호출하므로 count17인지 확인하는 코드를 삽입하면 된다.

커널을 컴파일하고 재부팅한다.

부팅 후, 아래와 같은 코드를 가지는 파일들을 만들었다.

hello_world.c :

#include <stdio.h>

int main() {
  printf("Hello World!\n");
}

ex.c :

#include <stdio.h>

int main() {
  printf("1234567890123456\n");
}

ex.c에서는 개행문자(\n)을 포함하여 17글자를 출력하도록 하였다.

로그 레벨이 낮아 printk 출력이 보이지 않으므로 8로 수정했다.

임의의 17글자 문자열을 printf로 출력하게 한다. "Hello World!"는 17자가 아니어서 printk가 호출되지 않지만, "01234..."는 마지막의 개행문자(\n)까지 총 17자 이므로 "length 17 string found"가 출력되었다.

9) You can call a system call indirectly with syscall().

write(1, "hi", 2);

can be written as

syscall(4, 1, "hi", 2); // 4 is the system call number for `write` system call

Write a program that prints "hello" in the screen using syscall.

Sol)

ex2.c :

#include <stdio.h>

int main() {
    syscall(4, 1, "hello", 5); // 4 is the system call number for `write` system call
}

10) Create a new system call, my_sys_call with system call number 17 (system call number 17 is one that is not being used currently). Define my_sys_call() just before sys_write() in fs/read_write.c. Write a program that uses this system call:

void main(){
    syscall(17); // calls a system call with syscall number 17
}

When the above program runs, the kernel should display

hello from my_sys_call

Sol)

  • To define a new system call with syscall number x
    • insert the new system call name in arch/x86/kernel/syscall_table_32.S at index x
    • define the function in appropriate file (such as fs/read_write.c)
      asmlinkage void my_sys_call(){
          printk("hello from my_sys_call\n");
      }
    • recompile and reboot

  • To use this system call in a user program
    void main(){
        syscall(x);
    }

10-1) Create another system call that will add two numbers given by the user.

Suppose 31 is an empty entry in sys_call_table.

arch/x86/kernel/syscall_table_32.S :

31번 자리에 새로운 system call인 my_sys_sum으로 변경해주었다.

fs/read_write.c :

ex2.c :

void main(){
    int sum;
    sum = syscall(31, 4, 9);  // suppose 31 is an empty entry in sys_call_table
    printf("sum is %d\n", sum);
}

11) Modify the kernel such that it displays the system call number for all system calls. Run a simple program that displays "hello" in the screen and find out what system calls have been called. Also explain for each system call why that system call has been used.

Suppose 31 is an empty entry in sys_call_table.

arch/x86/kernel/syscall_table_32.S :

31번 자리에 새로운 system call인 my_sys_call_num으로 변경해주었다.

fs/read_write.c :

arch/x86/kernel/entry_32.S :

syscall_call 아래에 my_sys_call_num를 호출하는 어셈블리 코드르 삽입한다. 함수에 첫 번째 인자로 시스템 콜 번호를 전달하기 위해 호출 전 pushl %eax를 한다. 함수 호출이 끝나면 popl %eax으로 레지스터 상태를 되돌려 놓았다.

위와 같이 시스템 함수들이 호출되는 것을 볼 수 있다.

12) What system calls are being called when you remove a file? Use system() function to run a Linux command as below. Explain what each system call is doing. You need to make f1 file before you run it. Also explain for each system call why that system call has been used.

아래와 같이 ex.c 파일을 만들었다.
ex.c :

#include <stdlib.h>

int main() {
    system("rm x");
    return 0;
}

system은 리눅스 명령어를 직접 실행하는 함수이다.
리눅스 명령어인 rm xx라는 이름을 가진 파일을 삭제(remove)하는 명령이다.

$ gcc -o ex ex.c
$ echo 8 > /proc/sys/kernel/printk
$ ./ex







사용된 시스템 콜에 번호와 대응되는 이름을 붙여 나열하면 아래와 같다.

number name
45 sys_brk
33 sys_access
5 sys_open
197 sys_fstat64
192 sys_mmap2
6 sys_close
5 sys_open
197 sys_fstat64
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
6 sys_close
192 sys_mmap2
243 sys_set_thread_area
125 sys_mprotect
125 sys_mprotect
125 sys_mprotect
91 sys_munmap
45 sys_brk
33 sys_access
5 sys_open
197 sys_fstat64
192 sys_mmap2
6 sys_close
5 sys_open
4 sys_write
197 sys_fstat64
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
6 sys_close
5 sys_open
3 sys_read
197 sys_fstat64
192 sys_mmap2
192 sys_mmap2
6 sys_close
5 sys_open
3 sys_read
197 sys_fstat64
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
6 sys_close
192 sys_mmap2
243 sys_set_thread_area
125 sys_mprotect
125 sys_mprotect
125 sys_mprotect
125 sys_mprotect
125 sys_mprotect
91 sys_munmap
45 sys_brk
33 sys_access
5 sys_open
197 sys_fstat64
192 sys_mmap2
6 sys_close
5 sys_open
3 sys_read
197 sys_fstat64
192 sys_mmap2
6 sys_close
5 sys_open
3 sys_read
197 sys_fstat64
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
192 sys_mmap2
6 sys_close
192 sys_mmap2
243 sys_set_thread_area
125 sys_mprotect
125 sys_mprotect
125 sys_mprotect
91 sys_munmap
119 sys_sigreturn
  • sys_access는 파일의 권한을 검사하는 함수이다.
  • sys_brk는 힙(데이터) 영역을 확장하거나 축소하는 함수이다.
  • sys_fstat64는 파일 정보를 읽는 함수이다.
  • sys_mmap2는 파일이나 장치를 메모리에 대응시키는 함수이다.
  • sys_set_thread_area는 thread-local storage를 조작하는 함수이다.
  • sys_mprotect는 메모리 영역에 대한 접근을 제어하는 함수이다.
  • sys_munmap은 mmap으로 만들어진 메모리 페이지를 해제하는 함수이다.
  • sys_sigreturn는 시그널 핸들러에서 값을 반환하고 스택 프레임을 정리하는 함수이다.

System Call 흐름을 보면 rm파일이 있는지, 파일에 대한 정보를 확인하고 파일을 실행한다. rm에서는 f1에 대한 정보를 확인하고 파일을 삭제한다.

sys_mmap2sys_munmap 기억장치에 저장되어 있는 파일에 접근하기 위해 사용된 것으로 보이며, 여러 프로그램이 동시에 한 파일에 접근해서 발생하는 문제를 막기 위해 sys_set_thread_areasys_mprotect으로 락을 걸어 하나의 쓰레드만 파일에 접근할 수 있게한 것으로 보인다.

13) Find rm.c in busybox-1.31.1 and show the code that actually removes f1. Note all linux commands are actually a program, and running rm command means running rm.c program. rm needs a system call defined in uClibc-0.9.33.2 to remove a file. You may want to continue the code tracing all the way up to "INT 0x80" in uClibc for this system call.

busybox-1.31.1/coreutils/rm.c :

#include "libbb.h"

int rm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
int rm_main(int argc UNUSED_PARAM, char **argv)
{
	int status = 0;
	int flags = 0;
	unsigned opt;

	opt = getopt32(argv, "^" "fiRrv" "\0" "f-i:i-f");
	argv += optind;
	if (opt & 1)
		flags |= FILEUTILS_FORCE;
	if (opt & 2)
		flags |= FILEUTILS_INTERACTIVE;
	if (opt & (8|4))
		flags |= FILEUTILS_RECUR;
	if ((opt & 16) && FILEUTILS_VERBOSE)
		flags |= FILEUTILS_VERBOSE;

	if (*argv != NULL) {
		do {
			const char *base = bb_get_last_path_component_strip(*argv);

			if (DOT_OR_DOTDOT(base)) {
				bb_error_msg("can't remove '.' or '..'");
			} else if (remove_file(*argv, flags) >= 0) {
				continue;
			}
			status = 1;
		} while (*++argv);
	} else if (!(flags & FILEUTILS_FORCE)) {
		bb_show_usage();
	}

	return status;
}

rm 프로그램의 코드는 위와 같다. 각종 플래그를 확인하고, remove_file 함수를 호출해 파일을 삭제한다.

`busybox-1.31.1//libbb/remove_file.c:

#include "libbb.h"

int FAST_FUNC remove_file(const char *path, int flags)
{
	struct stat path_stat;

	if (lstat(path, &path_stat) < 0) {
		if (errno != ENOENT) {
			bb_perror_msg("can't stat '%s'", path);
			return -1;
		}
		if (!(flags & FILEUTILS_FORCE)) {
			bb_perror_msg("can't remove '%s'", path);
			return -1;
		}
		return 0;
	}

	if (S_ISDIR(path_stat.st_mode)) {
		DIR *dp;
		struct dirent *d;
		int status = 0;

		if (!(flags & FILEUTILS_RECUR)) {
			bb_error_msg("'%s' is a directory", path);
			return -1;
		}

		if ((!(flags & FILEUTILS_FORCE) && access(path, W_OK) < 0 && isatty(0))
		 || (flags & FILEUTILS_INTERACTIVE)
		) {
			fprintf(stderr, "%s: descend into directory '%s'? ",
					applet_name, path);
			if (!bb_ask_y_confirmation())
				return 0;
		}

		dp = opendir(path);
		if (dp == NULL) {
			return -1;
		}

		while ((d = readdir(dp)) != NULL) {
			char *new_path;

			new_path = concat_subpath_file(path, d->d_name);
			if (new_path == NULL)
				continue;
			if (remove_file(new_path, flags) < 0)
				status = -1;
			free(new_path);
		}

		if (closedir(dp) < 0) {
			bb_perror_msg("can't close '%s'", path);
			return -1;
		}

		if (flags & FILEUTILS_INTERACTIVE) {
			fprintf(stderr, "%s: remove directory '%s'? ",
					applet_name, path);
			if (!bb_ask_y_confirmation())
				return status;
		}

		if (status == 0 && rmdir(path) < 0) {
			bb_perror_msg("can't remove '%s'", path);
			return -1;
		}

		if (flags & FILEUTILS_VERBOSE) {
			printf("removed directory: '%s'\n", path);
		}

		return status;
	}

	/* !ISDIR */
	if ((!(flags & FILEUTILS_FORCE)
	     && access(path, W_OK) < 0
	     && !S_ISLNK(path_stat.st_mode)
	     && isatty(0))
	 || (flags & FILEUTILS_INTERACTIVE)
	) {
		fprintf(stderr, "%s: remove '%s'? ", applet_name, path);
		if (!bb_ask_y_confirmation())
			return 0;
	}

	if (unlink(path) < 0) {
		bb_perror_msg("can't remove '%s'", path);
		return -1;
	}

	if (flags & FILEUTILS_VERBOSE) {
		printf("removed '%s'\n", path);
	}

	return 0;
}
  • 함수가 실행되면 입력한 경로가 폴더인지 확인한다.
    • 경로가 폴더라면 재귀적으로 함수를 실행한다.
    • 경로가 파일이라면 rmdir을 호출해 파일을 삭제한다.

uClibc-0.9.33.2/libc/sysdeps/linux/common/rmdir.c :

#include <sys/syscall.h>
#include <unistd.h>

_syscall1(int, rmdir, const char *, pathname)
libc_hidden_def(rmdir)

rmdir의 실제 코드는 uClibc에 있다.

_syscall1를 따라가보자.

uClibc-0.9.33.2/libc/sysdeps/linux/common/bits/syscalls-common.h :

#define _syscall1(args...)  SYSCALL_FUNC(1, args)

#define SYSCALL_FUNC(nargs, type, name, args...)                    \
type name(C_DECL_ARGS_##nargs(args)) {                              \
    return (type)INLINE_SYSCALL(name, nargs, C_ARGS_##nargs(args)); \
}

#define INLINE_SYSCALL(name, nr, args...) INLINE_SYSCALL_NCS(__NR_##name, nr, args)

#define INLINE_SYSCALL_NCS(name, nr, args...)                       \
(__extension__                                                      \
 ({                                                                 \
    INTERNAL_SYSCALL_DECL(__err);                                   \
    (__extension__                                                  \
     ({                                                             \
       long __res = INTERNAL_SYSCALL_NCS(name, __err, nr, args);    \
       if (unlikely(INTERNAL_SYSCALL_ERROR_P(__res, __err))) {      \
        __set_errno(INTERNAL_SYSCALL_ERRNO(__res, __err));          \
        __res = -1L;                                                \
       }                                                            \
       __res;                                                       \
      })                                                            \
    );                                                              \
  })                                                                \
)
  • 위 함수는 C 매크로 함수이기 때문에 실제 함수가 아니지만 호출한다고 설명하겠다.
  • _syscall1SYSCALL_FUNC를 호출한다.
  • SYSCALL_FUNCINLINE_SYSCALL를 호출하고,
  • INLINE_SYSCALLINLINE_SYSCALL_NCS를 호출한다.
  • 마지막으로 INLINE_SYSCALL_NCS에서는 INTERNAL_SYSCALL_NCS를 호출한다.

uClibc-0.9.33.2/libc/sysdeps/linux/i386/bits/syscalls.h

#define INTERNAL_SYSCALL_NCS(name, err, nr, args...)    \
(__extension__                                          \
 ({                                                     \
    register unsigned int resultvar;                    \
    __asm__ __volatile__ (                              \
        LOADARGS_##nr                                   \
        "movl   %1, %%eax\n\t"                          \
        "int    $0x80\n\t"                              \
        RESTOREARGS_##nr                                \
        : "=a" (resultvar)                              \
        : "g" (name) ASMFMT_##nr(args) : "memory", "cc" \
    );                                                  \
    (int) resultvar;                                    \
  })                                                    \
)

코드를 보면 알 수 있듯, INTERNAL_SYSCALL_NCSint $0x80으로 rmdir의 인터럽트를 생성하는 어셈블리 코드를 생성한다. 이때, x80은 10진수 128로 System Call의 인터럽트 번호이다.