UNIX v6로 배우는 커널의 원리와 구조 (한빛미디어)

http://v6.cuzuco.com/v6.pdf






##### 14. 시스템 부팅




<개요>


- 커널 프로그램을 메모리로 읽어야 함(약 40KByte)

- 당시에는 메모리 용량이 작아서 ROM에 저장해 놓을 수 없었음

=> 단계를 나누어 작은 크기부터 읽은 후 큰 크기의 프로그램을 읽는 점진적 형태로 부팅을 수행

1. 부트로더를 읽는 프로그램을 먼저 메모리로 올리고,

2. 부트로더를 읽고,

3. 실제 커널 프로그램을 읽음




<Unix V6의 부팅과정>


1. ROM에 저장되어있는 Boot Strap Loader Program에서, Root Disk의 블록번호 0에 저장된 Boot Strap Program을 읽어서 메모리의 0x0으로 읽어옴

* Boot Strap Program은 시스템 관리자가 /etc/mkfs을 실행하여 파일 시스템을 구축할 때 블록 디바이스의 0번째 블록에 위치하도록 설정


2. Boot Strap Program은 Root Disk의 fs에서 /Unix나 /rkunix에 있는 Kernel Program의 본체를 메모리의 0x0으로 계속하여 읽어서 실행


3. kernel은 시스템을 초기화 함



<부팅, low.s>

--------------------------------------------------------------------------

/*

 *    커널 본체(?)의 하위 어드레스 부분

*/


1.    . = 0^.                            // 프로그램이 메모리로 로드될 때 주소가 0x0 임

2.        br    1f                       // 최초 실행 명령어, label 1로 brach

3.        4                               

4.        

5.        /* 중간 생략 */

6.

7.    . = 40^.

8.    .global    start,    dump   

9.    1:    jmp    start               // start로 jump

10.         jmp    dump

--------------------------------------------------------------------------





### 14.1.1 start


<start, m40.s> 

--------------------------------------------------------------------------

/*

 *    <처리내용>

 *

 *    1. 커널 APR 초기화

 *        - 최초의 커널공간을 설정

 *        - 커널 APR(Active Page Register, PAR과 PDR) 설정 (참고. p460, 표 14.1 커널 APR 초기 설정)

 *            a. APR0~5 : 물리메모리의 0~0137777를 할당. 물리어드레스 = 가상어드레스영역. 앞부분에 커널프로그램의 본체가 로드 됨

 *            b. APR6 : 커널 프로그램 뒤의 1KByte 영역 할당. proc[0]의 PRDA(user 구조체와 커널스택)이 할당

 *                            현재 실행 중인 프로세스의 PRDA이며, 프로세스 전환히 함께 전환 됨

 *            c. APR7 : 물리메모리의 최상위 영역 할당(0760000~07777777)

 *                            PDP-11/40이나 주변 디바이스 레지스터를 할당하여 PDP-11/40과 주변장치를 제어

 *            d. SP는 APR6 영역의 맨 끝을 가리키도록 함

  *        - 커널 APR은 PAR6을 제외하고 시스템 동작 중에는 값이 바뀌지 않음

 *            => 실행 프로세스의 PAR6은, user 구조체로 접근할 수 있는 전역변수 u(0140000)를 사용하여         

 *                해당 data segment의 physical memory address로 설정 (참조. p65, 그림 2.13 물리 어드레스 계산)

 *            (PAR, Page Address Register, 11~0 bit, physical memory block address의 base address register, 64bit 단위)

 * 

 *    2. MMU 초기화

 *        - SR0을 설정하여 MMU를 활성화 하면 "physical address <-> logical address"가 시작 됨 (가상메모리 사용을 시작 함)

 *            MMU는 SR0과 SR2라는 Status Register를 가짐 (참고. p41)

 *            SR0 : 메모리 관리 유효화 플래그와 에러 정보에 사용

 *            SR2 : 실행할 명령어의 16bit logical address

 *        - 커널 프로그램의 bss와 proc[0]의 PPDA를 0으로 초기화 (참고. p59)

 *                PPDA : 프로세스의 메모리 영역 중 Data Segment의 "user구조체 + 커널 스택 영역" 

 *                bss : 프로세스의 메모리 영역 중 Data Segment의 Data 영역에 있는 정적 변수가 위치하는 영역

 *        - 이전 모드를 user mode로 설정한 후 main() 실행 

 * 

 *    3. miain() 수행

 * 

*/


0610 /* ------------------------- */ 


0611 .globl start, _end, _edata, _main 

0612 start: 


/* MMU가 이미 활성화 되어있으면 비활성화가 될 때까지 진행하지 않음 */

0613 bit $1,SSR0 / SSR0 : SR0의 어드레스, SR0[0] bit가 서 있으면, MMU에서 메모리 관리 활성화 (SSRO = 177572)

0614 bne start / loop if restart 

0615 reset 

0616 


0617 / initialize systems segments, 커널 APR0-5의 초기화

0618 

0619 mov $KISA0,r0 / KISA0 = 172340, 커널 APR의 PAR

0620 mov $KISD0,r1 / KISD0 = 172300, 커널 APR의 PDR

0621 mov $200,r4 

0622 clr r2 / r2 클리어 

0623 mov $6,r3 


0624 1: 

0625 mov r2,(r0)+ 

0626 mov $77406,(r1)+ / 4k rw

0627 add r4,r2 

0628 sob r3,1b 

0629 

/*

* APR은 0~7까지 8쌍이 존재하며, 한 쌍의 APR은 한 페이지에 대응

* _edata : 데이터영역, _etext : 텍스트영역, _end : bss영역의 하한 어드레스를 가리킴 

* => 프로그램 컴파일 시 링커에 의해서 심볼 테이블로 저장

*/

0630 / initialize user segment, 커널 APR6 초기화

0631 

0632 mov $_end+63.,r2 

0633 ash $-6,r2 

0634 bic $!1777,r2 / PAR6 설정값 계산

0635 mov r2,(r0)+ / ksr = sysu / APR이 64byte 단위로 물리 어드레스를 관리하므로 반올림 할 때

0636 mov $USIZE-1\<8|6,(r1)+ / 6비트 오른쪽으로 시프트하여 하위 10bit를 남기고 0으로 초기호 

0637 

0638 / initialize io segment, 커널 APR7 초기화

0639 / set up counts on supervisor segments 

0640 

0641 mov $IO,(r0)+ 

0642 mov $77406,(r1)+ / rw 4k 

0643 

0644 / get a sp and start segmentation 

0645 / SP 초기화 및 MMU 활성

0646 mov $_u+[USIZE*64.],sp 

0647 inc SSR0 

0648 

0649 / clear bss

0650 / bss 활성화

0651 mov $_edata,r0  

0652 1:

0653 clr (r0)+

0654 cmp r0,$_end

0655 blo 1b

0656

0657 / clear user block

0658 / proc[0] user 커널 스택 영역을 0으로 초기화 

0659 mov $_u,r0

0660 1:

0661 clr (r0)+

0662 cmp r0,$_u+[USIZE*64.]

0663 blo 1b

0664

0665 / set up previous mode and call main

0666 / on return, enter user mode at 0R

0667 / main() 호출

0668 mov $30000,PS

0669 jsr pc,_main

0670 mov $170000,-(sp)

0671 clr -(sp)

0672 rtt


--------------------------------------------------------------------------



### 14.1.2 main() 



<처리내용>


1. 메모리 초기화


- proc[0]의 PPDA이하를 0으로 초기화 한 후 빈 영역의 관리를 위해서 coremap[]에 추가.

map 구조체 : 물리 메모리와 스와프 영역의 빈 영역을 관리 (빈 영역의 어드레스 크기를 나타냄) (참고. p134)

(coremap[] : 물리 메모리 관리영역(APR의 최소단위인 64bytes 단위) , swapmap[] : swap 메모리 관리영역(512bytes, 블록단위)


- MAXMEM 설정

: 실제 빈 영역의 크기를 나타내는 maxmem에는 실제 빈 영역의 크기와 MAXMEM 중 더 작은 값을 설정


int maxmem;    (system.h, maxmem)

#define MAXMEM    (64*32)    (param.h, MAXMEM)


2. swap 영역 초기화


- swapmap[]

- mfree() 확보

- swplo 블록에서 mswap 블록까지의 영역을 swapswap에 swap 영역으로 등록


(conf.c)

int swplo    4000;

int nswap    872;


3. clock 장치 초기화


4. proc[0] 생성

- proc[0] : 시스템 프로세스 스케줄러


5. I/O자원 초기화


6. proc[1] 생성 

- proc[1] : /etc/init을 실행하는 사용자 프로세스로 inode[]에 넣어둔 명령어들을 실행


* inode : 파일크기, 접근권한, 데이터가 저장된 블록 디바이스의 블록 번호정보 등을 포함하는 파일의 속성 데이터

블록 디바이스에 저장되어 있으며, 커널은 파일을 사용하기 위해 해당 파일의 inode를 읽음




<inode[], ken/main.c>


1511 /* 1561 UISD->r[0] = 077406; 

1512 * Icode is the octal bootstrap 

1513 * program executed in user mode

1514 * to bring up the system. 

1515 */ 

1516 int icode[] 

1517 {

1518     0104413, /* sys exec; init; initp */ 

1519     0000014, 

1520     0000010, 

1521     0000777, 

1522     0000014, /* initp: init; 0 */ 

1523     0000000, 

1524     0062457, /* init: */ 

1525     0061564, 

1526     0064457, 

1527     0064556, 

1528     0000164, 

1529 };



=> C언어 판

1    char *init = "/etc/init";

2    execl(init, init, 0);

3    while(1);



< main(), ken/main.c >


1550 main() 

1551 { 

1552     extern schar; 

1553     register i, *p; 

1554 

1555     /* 

1556     *     zero and free all of core  메모리와 스택영역 초기화

1557     */ 

1558 

1559     updlock = 0; 

1560     i = *ka6 + USIZE;                 // i에 proc[0]의 PRDA 영역의 뒤에 어드레스를 설정

ka6은 커널의 APR6의 어드레스(KISA6)이 설정되어 있으므로 "*ka6"으로 커널 APR6 값 가져오기

1561     UISD->r[0] = 077406;         // UISD, UISA : 사용자 APR0의 PAR, PDR의 어드레스 (seg.h에 define되어있음)

1562     for(;;) {     

1563         UISA->r[0] = i; 

1564         if(fuibyte(0) < 0)             // fuibyte() : 이전 모드의 가상 어드레스 공간의 데이터를 한 바이트만큼 복사하는 함수

 전 모드가 user mode이므로 fuibyte(0)은 사용자 APR0에 할당된 페이지의 처음부터 데이터를 복사하기 시작

1565             break; 

1566         clearseg(i);                       // 성공 시 수행루틴

1567         maxmem++;                     // coremap을 증가시키기 

1568         mfree(coremap, 1, i);        //  coremap 초기화

1569         i++; 

1570     } 

1571     if(cputype == 70) 

1572    for(i=0; i<62; i=+2) { 

1573         UBMAP->r[i] = i<<12; 

1574         UBMAP->r[i+1] = 0; 

1575     } 

1576     printf("mem = %l\n", maxmem*5/16); 

1577     printf("RESTRICTED RIGHTS\n\n"); 

1578     printf("Use, duplication or disclosure is subject to\n"); 

1579     printf("restrictions stated in Contract with Western\n"); 

1580     printf("Electric Company, Inc.\n"); 

1581 

1582     maxmem = min(maxmem, MAXMEM);

1583     mfree(swapmap, nswap, swplo); 

1584 

1585     /* 

1586     * set up system process, 프로세스 0 생성

1587     */ 

1588 

1589     proc[0].p_addr = *ka6;     

1590     proc[0].p_size = USIZE; 

1591      proc[0].p_stat = SRUN;

1592     proc[0].p_flag =| SLOAD|SSYS;     // SSYS : 시스템 프로세스, 스와프아웃의 대상이 되지 않음

1593     u.u_procp = &proc[0]; 

1594 

1595     /*

1596     * determine clock, 클록장치 초기화

1597     */ 

1598 

1599     UISA->r[7] = ka6[1]; /* io segment */        // 사용자 APR7에 커널 APR7 값을 설정 => fuiword()가 I/O 영역에 접근할 수 있도록 설정

1600    UISD->r[7] = 077406; 


// CLOCK1 : 전원주파수 클록 레지스터의 어드레스

// CLOCK2 :  프로그래머블 클록 자아치의 레지스터의 어드레스


< CLOCK, ken/main.c >

#define CLOCK1    0177546

#define CLOCK2    0172540


1601     lks = CLOCK1;                         

1602     if(fuiword(lks) == -1) {     // CLOCK1에 대해 fuiworkd()를 수행

1603         lks = CLOCK2;             // 실패하면 CLOCK2에 재수행

1604         if(fuiword(lks) == -1) 

1605             panic("no clock");     // CLOCK1, CLOCK2 둘 다 실패하면 pannic()으로 실행 중지

1606     } 

1607     *lks = 0115;     // 초기값이 왜 뒤에있을까.. 미리 설정되어있어야 하는게 아닐까? 전역변수 같은데 딴데서 먼저 했나보네~~

1608 

1609    /* 

1610     * set up ’known’ i-nodes, 자원 초기화

1611     */ 

1612 

1613     cinit(); 

1614     binit(); 

1615     iinit(); 

1616     rootdir = iget(rootdev, ROOTINO); 

1617     rootdir->i_flag =& ~ILOCK; 

1667     while(nt >= 128) { 

1618         u.u_cdir = iget(rootdev, ROOTINO); 

1619         u.u_cdir->i_flag =& ~ILOCK; 

1621         /* 

1622         * make init process             , 프로세스 1 생성

1623         * enter scheduling loop 

1624         * with system process 

1625         */ 

1626 

1627         if(newproc()) {         // proc[1]을 생성


//  proc[1]의 데이터영역의 어드레스 0에 inode[]를 복사하고 리턴

//

1628             expand(USIZE+1);             

1629             estabur(0, 1, 0, 0); 

1630             copyout(icode, 0, sizeof icode);   

1632             * Return goes to loc. 0 of user init 

1633             * code just copied out. 

1634             */ 

1635             return;     // return 되면 main()을 실행했던 위치로 돌아가서 값을 스택에 쌓고, rtt 명령을 실행

// rtt명령 : 스택의 처음부터 pc에 0, PSW에 0170000을 읽음

// 현모드, 이전모드가 사용자 모드 상태이며, 사용자 공간의 어드레스 0부터 명령이 실행

// 어드레스 0에 icode[]가 복사되어 있기 때문에 /etc/init을 실행하여 처리

1636         } 

1637         sched();         // proc[0]이 실행하여, 스와핑 대상이 없으므로 proc[0]은 sleep하고 제어권은 proc[1]로 이동

1638     } 

1639 /* ------------------------- */





### 14.1.3 /etc/init


* 사용자 프로그램, 자세한 내용은 UPM(8) 참조


1. 등록된 터미널에 대한 프로세스를 생성

2. 각 프로세스는 터미널의 다이얼 업 접속, 사용자의 로그인을 기다림. 

사용자가 로그인하면 쉘 프로그램을 실행

3. /etc/init는 이후 무한 루프로 들어가서 프로세스 처리를 계속


> 스케줄러는 sched(), switch() 실행을 계속 수행

> /etc/init : 등록된 터미널에 대응하는 프로세스를 생성하고 고아 프로세스의 처리 루프를 계속 수행

> 각 터미널에 대응하는 프로세스 터미널의 다이얼 업 접속과 사용자의 로그인을 기다림







'System > Linux Kernel' 카테고리의 다른 글

커널에서 원하는 값을 가지고오기 위해 clz를 하는 이유  (0) 2016.01.16
system(), fork()  (0) 2015.12.24
Device Tree, 리눅스 커널 4.0  (0) 2015.08.29
메모리관리  (0) 2015.08.18
인터럽트 / 트랩  (0) 2015.08.16

+ Recent posts