• linux0.11-内核启动流程


    bootsect


            上电后bios把bootsect放到了0x7c00的的地方;然后他自己又把自己移到了0x90000的地方,它是磁盘引导块程序,在磁盘的第一个扇区中的程序(0磁道 0磁头 1扇区 );将后续的setup.s代码从磁盘中加载到紧接着bootsect.s的地方;在显示屏上显示loading system 再将system模块加载到0x10000的地方;最后跳转到setup.s中去运行。

    1. ! 由BIOS把bootsect从某个固定的地址(0xFFFF0)拿到了内存中的某个固定地址(0x90000)
    2. ! 并且进行了一系列初始化操作
    3. ! 这个文件的作用:将后续的setup.s从磁盘中加载到紧接着执行的内存位置
    4. ! 在显示屏上显示loading,再将system加载到0x10000,最后跳转到setup中去操作
    5. !
    6. ! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
    7. ! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
    8. ! versions of linux
    9. !
    10. SYSSIZE = 0x3000
    11. !
    12. ! bootsect.s (C) 1991 Linus Torvalds
    13. !
    14. ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
    15. ! iself out of the way to address 0x90000, and jumps there.
    16. !
    17. ! It then loads 'setup' directly after itself (0x90200), and the system
    18. ! at 0x10000, using BIOS interrupts.
    19. !
    20. ! NOTE! currently system is at most 8*65536 bytes long. This should be no
    21. ! problem, even in the future. I want to keep it simple. This 512 kB
    22. ! kernel size should be enough, especially as this doesn't contain the
    23. ! buffer cache as in minix
    24. !
    25. ! The loader has been made as simple as possible, and continuos
    26. ! read errors will result in a unbreakable loop. Reboot by hand. It
    27. ! loads pretty fast by getting whole sectors at a time whenever possible.

    setup


            首先解析BIOS/BOOTLOADER传递来的参数。设置系统内核运行的LDT(局部描述符) IDT(中断描述符寄存器) 全局描述符GDT(设置全局描述符寄存器)。设置中断控制芯片,进入保护模式运行(svc32保护模式 设置寄存器中的值)跳转到system模块的最前面的head.s代码运行。

    1. 1. 解析BIOS/BOOTLOADER中传递来的参数
    2. 2. 设置系统内核运行的LDT 和IDT 全局描述符 各寄存器
    3. 3. 设置中断控制芯片 进入保护模式运行(svc32)
    4. 4. 跳转到system模块的最前面的代码运行(head.s)
    5. ! setup.s (C) 1991 Linus Torvalds
    6. !
    7. ! setup.s is responsible for getting the system data from the BIOS,
    8. ! and putting them into the appropriate places in system memory.
    9. ! both setup.s and system has been loaded by the bootblock.
    10. !
    11. ! This code asks the bios for memory/disk/other parameters, and
    12. ! puts them in a "safe" place: 0x90000-0x901FF, ie where the
    13. ! boot-block used to be. It is then up to the protected mode
    14. ! system to read them from there before the area is overwritten
    15. ! for buffer-blocks.
    16. !
    17. ! NOTE! These had better be the same as in bootsect.s!

    head

    1. //1. 加载内核运行时的各数据段寄存器,重新设置中断描述符表
    2. //2. 开启内核正常运行时的协处理器等资源
    3. //3. 设置内存管理的分页机制
    4. //4. 跳转到main.c开始运行
    5. /*
    6. * linux/boot/head.s
    7. *
    8. * (C) 1991 Linus Torvalds
    9. */
    10. /*
    11. * head.s contains the 32-bit startup code.
    12. *
    13. * NOTE!!! Startup happens at absolute address 0x00000000, which is also where
    14. * the page directory will exist. The startup code will be overwritten by
    15. * the page directory.
    16. */


    初始化IDT、GDT,设置内存分页,跳转到main函数。

    main


    1 设置内存、缓存大小,进行内存、陷阱门、字符设备等的初始化

    2 初始化做完后打开中断 

    3 从内核态转为用户态 move_to_user_mode

    4 利用fork创建进程0,在进程0中调用init函数,打开以终端控制台作为标准输入,之后创建标准输出、标准错误输出。 

    5 调用fork创建进程1,关闭文件句柄0,以/etc/rc作为标准输入,调用execve跳转到shell代码。

    6 父进程再次运行wait()等待

    1. /*
    2. * linux/init/main.c
    3. *
    4. * (C) 1991 Linus Torvalds
    5. */
    6. #define __LIBRARY__
    7. #include
    8. #include
    9. /*
    10. * we need this inline - forking from kernel space will result
    11. * in NO COPY ON WRITE (!!!), until an execve is executed. This
    12. * is no problem, but for the stack. This is handled by not letting
    13. * main() use the stack at all after fork(). Thus, no function
    14. * calls - which means inline code for fork too, as otherwise we
    15. * would use the stack upon exit from 'fork()'.
    16. *
    17. * Actually only pause and fork are needed inline, so that there
    18. * won't be any messing with the stack from main(), but we define
    19. * some others too.
    20. */
    21. static inline _syscall0(int,fork)
    22. static inline _syscall0(int,pause)
    23. static inline _syscall1(int,setup,void *,BIOS)
    24. static inline _syscall0(int,sync)
    25. #include
    26. #include
    27. #include
    28. #include
    29. #include
    30. #include
    31. #include
    32. #include
    33. #include
    34. #include
    35. #include
    36. static char printbuf[1024];
    37. extern int vsprintf();
    38. extern void init(void);
    39. extern void blk_dev_init(void);
    40. extern void chr_dev_init(void);
    41. extern void hd_init(void);
    42. extern void floppy_init(void);
    43. extern void mem_init(long start, long end);
    44. extern long rd_init(long mem_start, int length);
    45. extern long kernel_mktime(struct tm * tm);
    46. extern long startup_time;
    47. /*
    48. * This is set up by the setup-routine at boot-time
    49. 全局变量 都是在boot阶段读入的,然后放到宏定义中
    50. */
    51. #define EXT_MEM_K (*(unsigned short *)0x90002)
    52. #define DRIVE_INFO (*(struct drive_info *)0x90080)
    53. #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
    54. /*
    55. * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
    56. * and this seems to work. I anybody has more info on the real-time
    57. * clock I'd be interested. Most of this was trial and error, and some
    58. * bios-listing reading. Urghh.
    59. */
    60. #define CMOS_READ(addr) ({ \
    61. outb_p(0x80|addr,0x70); \
    62. inb_p(0x71); \
    63. })
    64. #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)
    65. static void time_init(void)//这一段代码起到从CMOS中读取时间信息的作用
    66. {
    67. struct tm time;
    68. do {
    69. time.tm_sec = CMOS_READ(0);
    70. time.tm_min = CMOS_READ(2);
    71. time.tm_hour = CMOS_READ(4);
    72. time.tm_mday = CMOS_READ(7);
    73. time.tm_mon = CMOS_READ(8);
    74. time.tm_year = CMOS_READ(9);
    75. } while (time.tm_sec != CMOS_READ(0));
    76. BCD_TO_BIN(time.tm_sec);//把CMOS中读出来的数据进行转换
    77. BCD_TO_BIN(time.tm_min);
    78. BCD_TO_BIN(time.tm_hour);
    79. BCD_TO_BIN(time.tm_mday);
    80. BCD_TO_BIN(time.tm_mon);
    81. BCD_TO_BIN(time.tm_year);
    82. time.tm_mon--;
    83. startup_time = kernel_mktime(&time);//存在startup_time这个全局变量中,并且之后会被JIFFIES使用
    84. }
    85. static long memory_end = 0;
    86. static long buffer_memory_end = 0;
    87. static long main_memory_start = 0;
    88. struct drive_info { char dummy[32]; } drive_info;
    89. //main函数 linux引导成功后就从这里开始运行
    90. void main(void) /* This really IS void, no error here. */
    91. { /* The startup routine assumes (well, ...) this */
    92. /*
    93. * Interrupts are still disabled. Do necessary setups, then
    94. * enable them
    95. */
    96. //前面这里做的所有事情都是在对内存进行拷贝
    97. ROOT_DEV = ORIG_ROOT_DEV;//设置操作系统的根文件
    98. drive_info = DRIVE_INFO;//设置操作系统驱动参数
    99. //解析setup.s代码后获取系统内存参数
    100. memory_end = (1<<20) + (EXT_MEM_K<<10);
    101. //取整4k的内存大小
    102. memory_end &= 0xfffff000;
    103. if (memory_end > 16*1024*1024)//控制操作系统的最大内存为16M
    104. memory_end = 16*1024*1024;
    105. if (memory_end > 12*1024*1024)
    106. buffer_memory_end = 4*1024*1024;//设置高速缓冲区的大小,跟块设备有关,跟设备交互的时候,充当缓冲区,写入到块设备中的数据先放在缓冲区里,只有执行sync时才真正写入;这也是为什么要区分块设备驱动和字符设备驱动;块设备写入需要缓冲区,字符设备不需要是直接写入的
    107. else if (memory_end > 6*1024*1024)
    108. buffer_memory_end = 2*1024*1024;
    109. else
    110. buffer_memory_end = 1*1024*1024;
    111. main_memory_start = buffer_memory_end;
    112. #ifdef RAMDISK
    113. main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
    114. #endif
    115. //内存控制器初始化
    116. mem_init(main_memory_start,memory_end);
    117. //异常函数初始化
    118. trap_init();
    119. //块设备驱动初始化
    120. blk_dev_init();
    121. //字符型设备出动初始化
    122. chr_dev_init();
    123. //控制台设备初始化
    124. tty_init();
    125. //加载定时器驱动
    126. time_init();
    127. //进程间调度初始化
    128. sched_init();
    129. //缓冲区初始化
    130. buffer_init(buffer_memory_end);
    131. //硬盘初始化
    132. hd_init();
    133. //软盘初始化
    134. floppy_init();
    135. sti();
    136. //从内核态切换到用户态,上面的初始化都是在内核态运行的
    137. //内核态无法被抢占,不能在进程间进行切换,运行不会被干扰
    138. move_to_user_mode();
    139. if (!fork()) { //创建0号进程 fork函数就是用来创建进程的函数 /* we count on this going ok */
    140. //0号进程是所有进程的父进程
    141. init();
    142. }
    143. /*
    144. * NOTE!! For any other task 'pause()' would mean we have to get a
    145. * signal to awaken, but task0 is the sole exception (see 'schedule()')
    146. * as task 0 gets activated at every idle moment (when no other tasks
    147. * can run). For task0 'pause()' just means we go check if some other
    148. * task can run, and if not we return here.
    149. */
    150. //0号进程永远不会结束,他会在没有其他进程调用的时候调用,只会执行for(;;) pause();
    151. for(;;) pause();
    152. }
    153. static int printf(const char *fmt, ...)
    154. {
    155. va_list args;
    156. int i;
    157. va_start(args, fmt);
    158. write(1,printbuf,i=vsprintf(printbuf, fmt, args));
    159. va_end(args);
    160. return i;
    161. }
    162. static char * argv_rc[] = { "/bin/sh", NULL };
    163. static char * envp_rc[] = { "HOME=/", NULL };
    164. static char * argv[] = { "-/bin/sh",NULL };
    165. static char * envp[] = { "HOME=/usr/root", NULL };
    166. void init(void)
    167. {
    168. int pid,i;
    169. //设置了驱动信息
    170. setup((void *) &drive_info);
    171. //打开标准输入控制台 句柄为0
    172. (void) open("/dev/tty0",O_RDWR,0);
    173. (void) dup(0);//打开标准输入控制台 这里是复制句柄的意思
    174. (void) dup(0);//打开标准错误控制台
    175. printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
    176. NR_BUFFERS*BLOCK_SIZE);
    177. printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
    178. if (!(pid=fork())) {//这里创建1号进程
    179. close(0);//关闭了0号进程的标准输入输出
    180. if (open("/etc/rc",O_RDONLY,0))//如果1号进程创建成功打开/etc/rc这里面保存的大部分是系统配置文件 开机的时候要什么提示信息全部写在这个里面
    181. _exit(1);
    182. execve("/bin/sh",argv_rc,envp_rc);//运行shell程序
    183. _exit(2);
    184. }
    185. if (pid>0)//如果这个是0号进程
    186. while (pid != wait(&i))//就等待子进程退出
    187. /* nothing */;
    188. //如果走到这里,说明创建的1号进程失败了,且wait等待子进程结束了
    189. while (1) {
    190. //创建新的进程
    191. if ((pid=fork())<0) {//如果创建失败
    192. printf("Fork failed in init\r\n");
    193. continue;//重新创建 继续 走到pid=fork()
    194. }
    195. //如果创建成功
    196. if (!pid) {//子进程中执行
    197. close(0);close(1);close(2);//关闭上面那几个输入输出错误的句柄
    198. setsid();//重新设置id
    199. (void) open("/dev/tty0",O_RDWR,0);
    200. (void) dup(0);
    201. (void) dup(0);//重新打开
    202. _exit(execve("/bin/sh",argv,envp));//这里不是上面的argv_rc和envp_rc了是因为怕上面那种创建失败,换了一种环境变量来创建,过程和上面是一样的其实
    203. }
    204. //父进程等待子进程wait,子进程执行execve切换到新的进程中,不会走到这里
    205. while (1)
    206. if (pid == wait(&i))
    207. break;
    208. printf("\n\rchild %d died with code %04x\n\r",pid,i);
    209. sync();
    210. }
    211. _exit(0); /* NOTE! _exit, not exit() */
    212. }

    linux 0.11 进程,《Linux0.11内核完全注释》读书笔记之内核启动方式总结_暗夜猎手 Vayne的博客-CSDN博客Linux 内核初始化过程_~怎么回事啊~的博客-CSDN博客_linux 内核初始化

  • 相关阅读:
    实现定时器
    Java抽象类和接口
    域名解析常见问题(上)
    山西电力市场日前价格预测【2023-09-13】
    mysql group_concat 与 union 联合查询漏洞,数据列最大长度为341
    uniapp快速入门系列(4)- 微信小程序开发
    为什么AirtestIDE的selenium Window突然无法检索控件了?
    AR Engine光照估计能力,让虚拟物体在现实世界更具真实感
    第十五章 观察者模式
    Vue插值表达式及常用指令
  • 原文地址:https://blog.csdn.net/LIJIWEI0611/article/details/126349385