Linux 0.11 内核学习之main.c

1.之所以选择这么低的版本学习,答案是简单,高版本的代码量太大,对于我这样的初学者来说,就是瞎子摸象不会有什么感觉。开始吧!

2首先你需要在一个地方下载源码:OldLinux

3.分析:

  1 /*
  2  *  linux/init/main.c
  3  *
  4  *  (C) 1991  Linus Torvalds
  5  */
  6
  7 #define __LIBRARY__            //在unistd.h中,使用了#ifndef __LIBRARY__
  8 #include <unistd.h>            //包含unitsd.h
  9 #include <time.h>            // 结构体的定义,包含tm(结构体),时间函数定义
 10
 11 /*
 12  * we need this inline - forking from kernel space will result
 13  * in NO COPY ON WRITE (!!!), until an execve is executed. This
 14  * is no problem, but for the stack. This is handled by not letting
 15  * main() use the stack at all after fork(). Thus, no function
 16  * calls - which means inline code for fork too, as otherwise we
 17  * would use the stack upon exit from ‘fork()‘.
 18  *
 19  * Actually only pause and fork are needed inline, so that there
 20  * won‘t be any messing with the stack from main(), but we define
 21  * some others too.
 22  */
 23  /*--翻译--*/  //这个地方与boot目录下的bootsect.s,setup.s,head.s有关
 24  /*
 25  * 我们需要下面这些内嵌语句 - 从内核空间创建进程(forking)将导致没有
 26  * 写时复制(COPY ON WRITE)!!! 直到一个执行execve 调用。这对堆栈可
 27  * 能带来问题。处理的方法是在fork()调用之后不让main()使用任何堆栈。
 28  * 因此就不能有函数调用 - 这意味着fork 也要使用内嵌的代码,否则我
 29  * 们在从fork()退出时就要使用堆栈了。实际上只有pause 和fork 需要使用
 30  * 内嵌方式,以保证从main()中不会弄乱堆栈,但是我们同时还定义了其它
 31  * 一些函数
 32  */
 33 static inline _syscall0(int,fork)
 34 static inline _syscall0(int,pause)
 35 static inline _syscall1(int,setup,void *,BIOS)
 36 static inline _syscall0(int,sync)        //系统调用
 37
 38 #include <linux/tty.h>    //tty头文件,定义了有关tty_io,串行通信方面的参数,常数
 39 //所谓“串行通信”是指外设和计算机间使用一根数据信号线
 40 //数据在一根数据信号线上按位进行传输,每一位都占据一个固定的时间长度
 41 #include <linux/sched.h> //调度程序头文件,定义了任务结构和task_struct,第1一个初始任务的数据
 42 //还有一些以宏的形式定义的有关描述参数设置和获取的嵌入式汇编函数程序
 43 #include <linux/head.h>  //head头文件,定义了段描述的简单结构,和集合选择符常量
 44 #include <asm/system.h>  //系统头文件,以宏的形式定义了许多有关设置或修改
 45 //描述符/中断门等的嵌入式汇编子程序
 46 #include <asm/io.h>//io头文件,以宏的嵌入汇编程序形式定义对io端口操作的函数
 47
 48 #include <stddef.h>//标准定义头文件,定义了NULL,offsetof(TYPE,MEMBER)
 49 #include <stdarg.h>//标准参数头文件,以宏的形式定义变量参数列表,主要说明一个类型(va_list)
 50 // 和三个宏(va_list,va_arg和va_end),vsprintf vprintf , vfprintf
 51 #include <unistd.h>
 52 #include <fcntl.h> //文件控制头文件,用于文件及其描述符的操作控制常数符号的定义
 53 #include <sys/types.h>  //类型头文件,定义了基本的系统数据类型
 54
 55 #include <linux/fs.h> //文件系统头文件,定义文件表结构(file,buffer_head,m_inode 等)
 56
 57 static char printbuf[1024];
 58
 59 extern int vsprintf();
 60 extern void init(void);
 61 extern void blk_dev_init(void); //块设备初始化
 62 extern void chr_dev_init(void); //字符设备初始化
 63 extern void hd_init(void);    //硬盘初始化程序
 64 extern void floppy_init(void); //软盘初始化程序
 65 extern void mem_init(long start, long end); //内存管理程序初始化
 66 extern long rd_init(long mem_start, int length); //虚拟盘初始化
 67 extern long kernel_mktime(struct tm * tm); //建立内核时间
 68 extern long startup_time; //内核启动时间(开机时间)(秒)
 69
 70 /*
 71  * This is set up by the setup-routine at boot-time
 72  //一下数据是由setup.s程序在引导时间设置的
 73  */
 74 #define EXT_MEM_K (*(unsigned short *)0x90002) //1m以后的拓展内存大小
 75 #define DRIVE_INFO (*(struct drive_info *)0x90080) //硬盘参数表基地址
 76 #define ORIG_ROOT_DEV (*(unsigned short *)0x901FC) //根文件系统所在设备号
 77
 78 /*
 79  * Yeah, yeah, it‘s ugly, but I cannot find how to do this correctly
 80  * and this seems to work. I anybody has more info on the real-time
 81  * clock I‘d be interested. Most of this was trial and error, and some
 82  * bios-listing reading. Urghh.
 83  */
 84
 85 #define CMOS_READ(addr) ({ \   //这段宏读取cmos实时时钟信息
 86 outb_p(0x80|addr,0x70); \   //0x70是些端口号,0x80|addr是要读取CMOS内存地址
 87 inb_p(0x71); \   //0x71是读端口号
 88 })
 89
 90 #define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10) //将BSD码转换数字
 91
 92 static void time_init(void) //读取cmos中的信息,初始化全局变量startup_time
 93 {
 94     struct tm time;
 95
 96     do {
 97         time.tm_sec = CMOS_READ(0);
 98         time.tm_min = CMOS_READ(2);
 99         time.tm_hour = CMOS_READ(4);
100         time.tm_mday = CMOS_READ(7);
101         time.tm_mon = CMOS_READ(8);
102         time.tm_year = CMOS_READ(9);
103     } while (time.tm_sec != CMOS_READ(0));
104     BCD_TO_BIN(time.tm_sec);
105     BCD_TO_BIN(time.tm_min);
106     BCD_TO_BIN(time.tm_hour);
107     BCD_TO_BIN(time.tm_mday);
108     BCD_TO_BIN(time.tm_mon);
109     BCD_TO_BIN(time.tm_year);
110     time.tm_mon--;        //months since january - [0,11]
111     startup_time = kernel_mktime(&time);
112 }
113
114 static long memory_end = 0; // 机器具有的内存(字节数)
115 static long buffer_memory_end = 0; //高速缓存末端地址
116 static long main_memory_start = 0; //主内存(将用于分页)开始的位置
117
118 struct drive_info { char dummy[32]; } drive_info; //用于存放硬盘信息
119
120 void main(void)        /* This really IS void, no error here. */
121 {            /* The startup routine assumes (well, ...) this */
122 //此时中断仍然是关着,在必要的设置完成之后
123 //打开中断
124 /*
125  * Interrupts are still disabled. Do necessary setups, then
126  * enable them
127  */
128   // 下面这段代码用于保存
129  // 根设备号 -- ROOT_DEV; 高速缓存末端地址 -- buffer_memory_end
130  // 机器内存数 -- memory_end;主内存开始地址 -- main_memory_start
131      ROOT_DEV = ORIG_ROOT_DEV;
132      drive_info = DRIVE_INFO;
133     memory_end = (1<<20) + (EXT_MEM_K<<10); //内存大小=1Mb字节+扩展内存(k) * 1024字节
134     memory_end &= 0xfffff000;  //忽略不到4kb(1页)的内存数
135     if (memory_end > 16*1024*1024) //如果内存超过16Mb,则16Mb计
136         memory_end = 16*1024*1024;
137     if (memory_end > 12*1024*1024) //如果内存>12Mb,则设置缓冲末端4Mb
138         buffer_memory_end = 4*1024*1024;
139     else if (memory_end > 6*1024*1024) //否则如果内存 > 6Mb,则设置缓冲末端=2Mb
140         buffer_memory_end = 2*1024*1024;
141     else  //否则设置缓冲区末端=1Mb
142         buffer_memory_end = 1*1024*1024;
143     main_memory_start = buffer_memory_end; //主内存(用于分页使用)起始位置=缓冲区末端
144 #ifdef RAMDISK
145     main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
146 #endif
147     mem_init(main_memory_start,memory_end);
148     trap_init(); //陷阱门(硬件中断向量)初始化
149     blk_dev_init(); //块设备初始化
150     chr_dev_init(); //字符设备初始化
151     tty_init();//tty初始化
152     time_init();//设置开机启动时间,startup_time
153     sched_init(); //调度程序初始化
154     buffer_init(buffer_memory_end); //缓冲区初始化,建立内存链表
155     hd_init(); //硬盘初始化
156     floppy_init(); //软盘初始化
157     sti(); //设置完成,开启中断
158     move_to_user_mode(); //移到用户模式
159     if (!fork()) {        /* we count on this going ok */
160         init();
161     }
162 /*
163  *   NOTE!!   For any other task ‘pause()‘ would mean we have to get a
164  * signal to awaken, but task0 is the sole exception (see ‘schedule()‘)
165  * as task 0 gets activated at every idle moment (when no other tasks
166  * can run). For task0 ‘pause()‘ just means we go check if some other
167  * task can run, and if not we return here.
168  */
169  /*
170  * 注意!! 对于任何其它的任务,‘pause()‘将意味着我们必须等待收到一个信号才会返
171  * 回就绪运行态,但任务0(task0)是唯一的意外情况(参见‘schedule()‘),因为任务0 在
172  * 任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0‘pause()‘仅意味着
173  * 我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行‘pause()‘。
174  */
175     for(;;) pause();
176 }
177
178 static int printf(const char *fmt, ...)//使用变长参数,调用write系统调用
179 {
180     va_list args;
181     int i;
182
183     va_start(args, fmt);
184     write(1,printbuf,i=vsprintf(printbuf, fmt, args));
185     va_end(args);
186     return i;
187 }
188
189 static char * argv_rc[] = { "/bin/sh", NULL }; //调用执行程序时参数的字符串数组
190 static char * envp_rc[] = { "HOME=/", NULL };//调用执行程序时的环境字符串数组
191
192 static char * argv[] = { "-/bin/sh",NULL };
193 static char * envp[] = { "HOME=/usr/root", NULL };
194
195 void init(void)
196 {
197     int pid,i;
198
199     setup((void *) &drive_info); //读取硬盘信息
200     (void) open("/dev/tty0",O_RDWR,0);//用读写访问方式打开设备"/dev/tty0"
201     (void) dup(0); //复制句柄,产生句柄1号--stdout标准输出设备
202     (void) dup(0); //复制句柄,产生句柄2号--stderr标准出错输出设备
203     //输出一些信息
204     printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
205         NR_BUFFERS*BLOCK_SIZE);
206     printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
207     //下面的代码打开/etc/rc,然后执行/bin/sh,但是这里开辟了
208     //两个线程
209     if (!(pid=fork())) {
210         close(0);
211         if (open("/etc/rc",O_RDONLY,0))
212             _exit(1);
213         execve("/bin/sh",argv_rc,envp_rc);
214         _exit(2);
215     }
216     if (pid>0)
217         while (pid != wait(&i))
218             /* nothing */;
219 /*
220   * 如果执行到这里,说明刚创建的子进程的执行已停止或终止了。
221   * 下面循环中首先再创建一个子进程.如果出错,则显示“初始化
222   * 程序创建子进程失败”的信息并继续执行。对于所创建的子进
223   * 程关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创
224   * 建一个会话并设置进程组号,然后重新打开/dev/tty0 作为stdin,
225   * 并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但
226   * 这次执行所选用的参数和环境数组另选了一套。然后父进程再次
227   * 运行wait()等待。如果子进程又停止了执行,则在标准输出上显
228   * 示出错信息“子进程pid 停止了运行,返回码是i”,然后继续重
229   * 试下去…,形成“大”死循环
230   *
231   */
232     while (1) {
233         if ((pid=fork())<0) {
234             printf("Fork failed in init\r\n");
235             continue;
236         }
237         if (!pid) {
238             close(0);close(1);close(2);
239             setsid();
240             (void) open("/dev/tty0",O_RDWR,0);
241             (void) dup(0);
242             (void) dup(0);
243             _exit(execve("/bin/sh",argv,envp));
244         }
245         while (1)
246             if (pid == wait(&i))
247                 break;
248         printf("\n\rchild %d died with code %04x\n\r",pid,i);
249         sync();
250     }
251     _exit(0);    /* NOTE! _exit, not exit() */
252 }

4.参考

参考一

参考二

参考三

时间: 2024-11-03 05:43:09

Linux 0.11 内核学习之main.c的相关文章

Linux 0.12和Linux 0.11内核学习——Google邮件列表

亲,你在学习Linux 0.12或0.11内核吗?快来加入我们吧,就缺你了!!! 为什么选用邮件列表呢?因为赵炯博士那个论坛交流不是很方便,经常发了贴没人回,人气相比十年前论坛刚成立时弱了不少.很多人,很多元老级别的人物,消失了...再也没有出现过. 而QQ群很繁杂,比如你肯定会因为一些兴趣爱好加入一些QQ群,但是也就刚加进去或者自己有什么要问的时候说几句,之后就屏蔽了,因为每天都有人在聊天,什么内容都有,想退群却又怕以后有用,不退吧又很烦,只能屏蔽了潜水. 而邮件列表,你可以订阅主题,实时追踪

Linux网络编程&amp;内核学习

c语言: 基础篇 1.<写给大家看的C语言书(第2版)> 原书名: Absolute Beginner's Guide to C (2nd Edition) 原出版社: Sams 作者: (美)Greg Perry    [作译者介绍] 译者: 谢晓钢 刘艳娟 丛书名: 图灵程序设计丛书 C/C++系列 出版社:人民邮电出版社 ISBN:9787115216359上架时间:2009-12-10出版日期:2010 年1月开本:16开页码:308 说明:这本是入门最好的,最简单,最好懂 2.<

Linux 0.11下信号量的实现和应用

Linux 011下信号量的实现和应用 生产者-消费者问题 实现信号量 信号量的代码实现 关于sem_wait和sem_post sem_wait和sem_post函数的代码实现 信号量的完整代码 实现信号量的系统调用 测试用的应用程序的实现 Linux 0.11下信号量的实现和应用 1.生产者-消费者问题 从一个实际的问题:生产者与消费者出发,谈一谈为什么要有信号量?信号量用来做什么? 问题描述:现在存在一个文件”.\buffer.txt”作为一个共享缓冲区,缓冲区同时最多只能保存10个数.现

Linux 0.11中write实现

看了一下Linux 0.11版本write的实现,首先它在标准头文件unistd.h中有定义 int write(int fildes, const char * buf, off_t count); 接下来看write.c /* * linux/lib/write.c * * (C) 1991 Linus Torvalds */ #define __LIBRARY__ #include <unistd.h> //定义write的实现 _syscall3(int,write,int,fd,co

Linux 0.11 中字符设备的使用

Linux 0.11 字符设备的使用 一.概述 本文自顶向下一步步探索字符设备的读写是怎么完成的.通常我们在Linux应用程序中用open.read.write对各种类型的文件进行操作.我们可以从键盘输入,然后命令行窗口会显示你的输入,有输出的话则命令行窗口会显示输出.为什么所有的设备在Linux中都被看成是一个个文件,可以通过统一的read.write直接进行读写?文件句柄与终端设备有什么关联?为什么Linux允许多个控制终端登录?tty又是什么东西?读写时将发生哪些硬件中断,驱动程序是怎么回

基于 Docker 快速构建 Linux 0.11 实验环境

by Falcon of TinyLab.org 2015/05/02 简介 五分钟内搭建 Linux 0.11 的实验环境介绍了如何快速构建一个 Linux 0.11 实验环境. 本文介绍如何快速构建一个独立于宿主机的 Linux 0.11 实验环境,该实验环境可以用于任何操作系统的宿主开发机,将非常方便各类学生学习 Linux 0.11,本文只介绍 Ubuntu.在 Windows 和 Mac 下可以用 VirtualBox + Boot2Docker 来启动. 下文要求已经安装 git 和

Linux 0.12 内核管理存储器

其分段,用分段的机制把进程间的虚拟地址分隔开. 每一个进程都有一张段表LDT.整个系统有一张GDT表.且整个系统仅仅有一个总页表. 其地址翻译过程为: 程序中给出的32位地址(实际上被看做段内偏移地址),再依据代码段寄存器CS中的16位段选择子,可在GDT或LDT中查找对应的段描写叙述符.从段描写叙述符中提取段的基地址,与程序给出的32位地址相加.得到结果为线性地址. 依据此线性地址查找系统页文件夹表,再查二级或是多级页表,终于得到物理地址. 此方式系统仅仅有一个4G的线性地址空间由各进程共享(

《linux 内核完全剖析》编译linux 0.12 内核 Ubuntu 64bits 环境

我×...终于好了,大概3 4个小时吧...各种毛刺问题.终究还是闯过来了.... [email protected]:~/Downloads/linux-0.00-050613/linux-0.00$ make ld -s -x -M head.o  -o system > System.map dd bs=32 if=boot of=Image skip=1 16+0 records in 16+0 records out 512 bytes (512 B) copied, 0.000605

Linux 0.12内核与现代内核在内存管理上的区别

0.12内核的内存管理比较简单粗暴,内核只用了一个页目录,只能映射4G的线性空间,所以每个进程的虚拟空间(逻辑空间)只能给到64M,最多64个进程:每个进程都有对应的任务号nr,当一个进程需要分配进程空间时,只需要nr乘以64M就可以得出该进程空间的线性起始地址.然后该进程的代码段.数据段描述符里面的基址字段会被设定为(nr x 64M),同时可以为进程分配页目录项和页目录表用以承载映射关系. 之后如果进程要访问自己空间内的某个地址时就会首先用基地址与程序内32位偏移地址(逻辑地址)合成出线性地