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 |