Quality control (1)/Operating System

OS week3 Interrupt / ISR / IDT / atkbd_interrupt /

빈그레 2022. 10. 7. 22:33

 


 

Lecture 4: Interrupt

 

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).

 

 

 

 

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.

 

 

 

Exceptions have been assigned following interrupt numbers.

 

 

 

 

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.

 

 

 

 

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.

 

 






 




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
    - A user tries to remove a file 
System_call()=>sys_unlink(10) : 128

2) Change drivers/input/keyboard/atkbd.c as follows. //atkbd_interrupt의 ISR2 location
static irqreturn_t atkbd_interrupt(....){
   return IRQ_HANDLED;  // Add this at the first line
   .............
}

keyboard interrupt를 해결하는 ISR2의 위치로 가서 atkbd_interrupt() code에 return IRQ_HANDLED;를 추가하였다.

 


Recompile the kernel(=make bzImage->cp) and reboot with it. 

 

What happens and why does this happen? 

keyboard action이 먹히지 않는다. 본래는 keyboard 입력 즉, interrupt가 발생한 뒤에 키보드 입력 과정을 처리한 뒤 IRQ_HANDLED를 return 하도록 해야하는데 이러한 과정 없이 atkbd_interrupt()에서 IRQ_HANDLED를 바로 return하도록 코드를 수정하였기 때문에 external event인 keyboard입력이 저장 및 출력 되지 않고 아무런 작업도 실행되지 않은 것이다.

 

 

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. 

 

 'hit a key'의 interrupt가 발생되면 8259A의 1번 단자(IRQ1)에 연결된 keyboard controller가 interrupt signals를 보낸다.

 8259A는 33(base 32 + irq 1)이라는 번호를 cpu에게 전달한다. (keyboard interrupt number = int 33)

cpu는 int 33 interrupt가 발생했다고 판단한다.

 

인터럽트가 발생하면 cpu가 하드웨어적으로 일정 처리를 먼저하고, 

운영체제가 나머지 처리를 수행함으로써 처리를 완료한다. 

즉 cpu가 수행하는 명령어 다음에 INT 33이라는 명령어가 있었던 것처럼 처리한다. 

INT명령은 일종의 함수호출이므로 함수리턴에 대비하여 현재 위치로 돌아올 수 있도록 저장하고,

 INT 33의 처리위치로 점프한다.(이때, 함수의 처리위치는 인터럽트 벡터 TABLE에 적혀있다.)


Cpu는 주요 레지스터의 값을 저장된 위치에 기억시키고 INT 33의 처리루틴을 찾아 그곳으로 점프한다.

 Cpu가 점프한 위치에 운영체제의 준비단계인 ISR(interrupt service routine)1이 있다. ISR1은 인터럽트 처리를 위한 몇가지 사전 작업을 하고 실제 처리루틴을 다시 호출한다. 이때 실제 처리루틴을 ISR2라 부른다.


하드웨어 인터럽트의 ISR2는 디바이스 드라이버 코드가 해당된다. 이부분을 교체하면 디바이스의 행동을 변경할 수 있다. ISR2는 interrupt가 연속적으로 일어날 수 있으므로 top half(급한부분)와 botton half(덜 급한 부분)으로 나뉜다. 

botton half까지 처리가 완료되면 현 프로세스, 혹은 새로 선택된 프로세스의 register들을 회복해서 인터럽트가 발생되었던 지점으로 return한다.

 

 되돌리기

MY linux에 대해서는 atkbd_interrupt에서 작업 전에 return을 미리하여 keyboard action을 막은 것이 bzImage 에 적용 되었기 때문에 My linux에서 파일을 수정할 수 없으니 MY linux2에서 동일한 경로(drivers/input/keyboard/atkbd.c)로 들어가 해당 코드부분을 삭제하여 bzImage(My linux의 실행파일 위치)에 다시 업로드 시켜준다.

다음의 과정을 통해 My linux2안에서 atkbd_interrupt 에 대한 handle file인 atkbd.c를 수정하고

이를 My linux의 실행파일 위치에 복사하여 적용시켜줌으로써

reboot이후 My linux의 booting 과정에서 변경된 atkbd.c이 반영되어 다시 keyboard를 입력할 수 있게 된다.

 

My linux에서 keyboard입력이 정상적으로 수행되는 것을 통해 수정한 atkbd.c이 my linux의 executable file에 정상적으로 copy되었음을 확인할 수 있었다.

 

 

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.

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

The above means the console log level is 1, default printk log level is 4, and min conole log level is 1 and default console log level is 7.

Lower log level means higher priority. Since default log level has lower priority than console log level, using printk() will not show the message on the console. We change the console log level to lowest level so that printk() will be able to display message on the console. 

 

 

console의 log level이 더 높으면 printk()는 console에 message를 띄울 수 없다.

printk()가 console에 메세지를 display할 수 있도록 console의 log level을 가장 낮게 8로 설정해준다.

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

/proc/sys/kernel/printk shows the console log level, default log level, min and max log level.


# echo 8 > /proc/sys/kernel/printk
Above will set console log level to 8 which means all printk() message will appear on the console from now on. (Note the files in /proc file system are not real files. They are generated dynamically when needed.)

 

echo 명령어를 통해 console log level을 setting 해주고

cat을 통해 console log level이 8로 정상적으로 setting 되었음을 확인할 수 있었다.

 

modify atkbd_compat_scancode

atkbd.c file 내부에서 scancode를 검색하여 위 내용을 찾았다.

scancode의 인자로 받는 code의 값을 print하도록 printk("%x pressed\n", code);를 추가하여

keyboard를 통해 눌러진 것을 print하도록 하였다.

 

reboot 이후 "x pressed" .

reboot이후 console log level이 높아서 printk가 수행되지 않아

echo를 통해 console log levle을 8로 하여 priority를 낮춰주니 "1c pressed"가 출력되었고

이후 keyboard가 pressed될 때 마다 그에 맞는 x가 출력되었다.

 

 

"x pressed" 이전으로 되돌리기

console log level에 따른 출력 여부를 확인한 이후 다른 linux(본인은 gentoo로 들어갔음)로 들어가서 

atkbd.c 파일에 들어가서 "x pressed"를 추가해주었던 코드를 삭제해 my linux의 executable file인 bzImage에 copy하여 이전상태로 만들어주었다.

 

 

reboot 하고  My linux 에서 확인

"x pressed"삭제 후 echo를 통해 console log level을 8로 높여주어도

keyboard pressed에 대해 print되는 것이 없어졌음을 확인할 수 있었다.

 

 


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?

 

code++

scancode 함수에서 keyboard interrupt로서 들어온 입력 'code'에 대하여 1씩 증가하는 코드 "code++"를 추가해주었다.

compile&reboot이후 keyboard 입력을 다시 시도해보겠다.

 

login화면부터 keyboard로부터 들어온 입력 'code'에 대하여 1 증가된 즉, keyboard 상에서 한 칸 오른쪽에 있는 것들이

입력되었다. 따라서 root를 치기 위해서는 번호가 1씩 낮은 (keyboard상에서 한 칸씩 왼쪽에 있는)  eiir을 입력해야하고 'enter'역시 enter 좌측에 있는 keyboard를 입력해야한다.

 

너,,무 불편하기 때문에 다른 linux로 돌아가서 bzImage를 수정하여 다시 My linux 로 돌아와야겠다...

 

Gentoo로 들어와서 atkbd.c의 코드를 수정하고 my linux의 executable file 위치인 bzImage로 copy해주어  reboot이후 

my linux에서 keyboard가 정상적으로 입력되도록 돌려놓았다.

 


5-1) Define a function "mydelay" in init/main.c which whenever called will stop the booting process until you hit 's'.

5-2)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 boo/ting process. 

5-3)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
    .............
}

 

main.c modification

main.c 에 mydelay 함수를 정의해주고

문제에서 주어진 위치(main.c의 kernel_init함수 안에 do_basic_setup다음줄)에 호출해주었다.

 

 

atkbd.c modificaiton

if(code==31) exit_mydelay=1;

atkbd.c 에서 extern 선언을 하고 atkbd_interrupt 함수 내부에 위와 같은 코드를 추가하여

s의 scancode값인 31이 들어올 경우 exit_delay를 1로 바꾸어 mydelay를 탈출할 수 있도록 코드를 수정하고 compile한다.

 

 

init/main c에 mydelay함수를 추가하여 이를 kernel_init에 호출해주었기 때문에 

reboot하여 my linux로 들어가면 booting과정에서 login이 뜨기 전에 위와 같이 출력되고,

's'입력하면 atkbd.c에 추가한 if문에 의하여 exit_mydelay가 1이 되기 때문에

mydelay의 for문이 break되며 함수를 탈출하여 다음 booting과정으로 넘어간다.

 

 

 

5-4) Add mydelay before do_basic_setup(). What happens and why? 
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
    .........
}

 

mydealy를 main.c의 kernle_init함수에서 do_basic_setup 호출 이전에 호출해보았다.

recompile하고 reboot를 진행한다.

 

do_basic_setup 이후에 있는 mydelay는 실행되지 않고 이전에 있는 mydelay에 대해서만 함수가 실행되었다.

그리고 아무것도 입력되지 않는다..왜지???

왜지? 잘 모름 ㅡㅡ ㅋㅋ 

일단 다음 과제를 해야하므로 다른 리눅스로 들어가서 해당 코드를 수정해주었다...



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

keyboard에 입력된 값을 모니터에 보이도록 하는 실질적 함수 찾기

"x pressed"를 출력할 때 atkbd.c의 위 코드부분에서 modification을 하였으므로

keyboard 입력에 대한 출력은 위 코드 부분에서 이루어질 것이라 예상하였다.

 

 

input_event -> input_handle_evnet -> input_pass_event

위 함수들을 거치며 keyboard input이 모니터에 출력되는 과정을 확인하였다.

input_pass_event함수에서 결국 handle의 구조체의 event를 호출하는 것을 보아 atkbd_interrupt내에서 input_event가 key를 모니터에 띄우는 것이라 예상할 수 있다.

 

 

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  location

 ISR1은 arch/x86/kernel/entry_32.S로 모두 동일하다

 

- ISR2  location

 ISR2는 compile time 에 결정되기 때문에 linux source 에서는 함수가 바로 존재하지 않고 함수를 만드는 코드만 확인할 수 있다.

 

os interrupt는 4주차에 이어서 다루도록 하겠다~! (너무 많아용~)
week4에서 만나요~!~ 안녀엉~!~!