- 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 callsread(...) |
int 128 (syscall num 3) |
system_call() => sys_read() |
An application callswrite(...) |
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 runsx=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).
- 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.
- 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 |
...... | ...... | ...... |
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.
Interrupts are first handled by the CPU, and then the operating system takes care of the rest of things.
- 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 address0x10200
, the ISR for timer interrupt is located at address0x10200
, which means whenever the timer ticks, the cpu jumps to address0x10200
. - If
IDT[33]
indicates address0x10300
, the ISR for keyboard is located at0x10300
. - 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 inarch/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.
- 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 inarch/x86/kernel/i8259_32.c/native_init_IRQ()
, and by callingset_trap_gate()
for most of the exceptions andset_system_gate()
for system call interrupts inarch/x86/kernel/traps_32.c/trap_init()
. - Device drivers write their ISR2's for hardware interrupts in
irq_desc[]
table by callingrequest_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[]
inarch/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 |
....................... |
Creating a new system call : 2 steps
- find empty
sys_call_table
entry (sys_ni_syscall
) :x
-
- write new system call name:
my_syscall
- write new system call name:
-
- define
my_syscall
(in appropriate file such asfs/read_write.c
)
asmlinkage void my_syscall(){ printk("hello from my_syscall\n"); }
- define
- recompile and reboot
void main(){
syscall(x);
}
==> syscall(x)
==> mov eax, x
int 0x80
==> system_call
==> my_syscall
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
$ 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
.............
}
$ 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
code
를 printk
로 출력할 수 있도록 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'를 입력하면 부팅이 이어서 진행된다.
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'를 입력할 수 없었고, 이후의 부팅 과정을 이어서 실행할 수 없었다.
drivers/input/keyboard/atkbd.c
:
키보드로 입력한 key를 찾는 Prob 3)에서 drivers/input/keyboard/atkbd.c
에 printk("%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 |
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
:
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번이다.
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
를 호출하므로 count
가 17
인지 확인하는 코드를 삽입하면 된다.
커널을 컴파일하고 재부팅한다.
부팅 후, 아래와 같은 코드를 가지는 파일들을 만들었다.
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"가 출력되었다.
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.
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
- To define a new system call with syscall number
x
- To use this system call in a user program
void main(){ syscall(x); }
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 x
는 x
라는 이름을 가진 파일을 삭제(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_mmap2
과 sys_munmap
기억장치에 저장되어 있는 파일에 접근하기 위해 사용된 것으로 보이며,
여러 프로그램이 동시에 한 파일에 접근해서 발생하는 문제를 막기 위해 sys_set_thread_area
와 sys_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.
#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
함수를 호출해 파일을 삭제한다.
#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
을 호출해 파일을 삭제한다.
#include <sys/syscall.h>
#include <unistd.h>
_syscall1(int, rmdir, const char *, pathname)
libc_hidden_def(rmdir)
rmdir
의 실제 코드는 uClibc
에 있다.
_syscall1
를 따라가보자.
#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 매크로 함수이기 때문에 실제 함수가 아니지만 호출한다고 설명하겠다.
_syscall1
는SYSCALL_FUNC
를 호출한다.SYSCALL_FUNC
는INLINE_SYSCALL
를 호출하고,INLINE_SYSCALL
는INLINE_SYSCALL_NCS
를 호출한다.- 마지막으로
INLINE_SYSCALL_NC
S에서는INTERNAL_SYSCALL_NCS
를 호출한다.
#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_NCS
는 int $0x80
으로 rmdir
의 인터럽트를 생성하는 어셈블리 코드를 생성한다. 이때, x80
은 10진수 128로 System Call의 인터럽트 번호이다.