linux.docx

上传人:b****7 文档编号:11189975 上传时间:2023-02-25 格式:DOCX 页数:18 大小:376.04KB
下载 相关 举报
linux.docx_第1页
第1页 / 共18页
linux.docx_第2页
第2页 / 共18页
linux.docx_第3页
第3页 / 共18页
linux.docx_第4页
第4页 / 共18页
linux.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

linux.docx

《linux.docx》由会员分享,可在线阅读,更多相关《linux.docx(18页珍藏版)》请在冰豆网上搜索。

linux.docx

linux

 

 

Linux操作系统源码分析

 

学院计算机工程学院

专业计算机科学与技术

年级班别09级计算机科学与技术1班

学号2009404010125

学生姓名

指导教师李永

 

2012年6月10日

目录

第一章引言1

1.1设计目的1

1.2任务与分析1

1.3Linux内核的特征1

第二章内核分析3

2.1系统内核体系结构3

2.2代码分析4

2.3调用关系图7

第三章配置和编译内核16

3.1内核支持(编译)模式16

3.2配置系统内核16

第四章心得体会18

参考文献18

第一章引言

1.1设计目的

操作系统包含了多个部分或者组件,最核心的部分是内核。

其他的部分用来帮助内核完成计算机资源的管理和应用程序的控制。

操作系统控制了计算机上运行的各种应用程序。

没有操作系统各类函数的调用,应用程序就无法执行。

因而,对操作系统的研究是很有必要的。

Linux操作系统是使用很广泛的,高质量的一个操作系统,而且作为一个开源的系统,可以很方便的查看起代码并进行分析,有利于更好的认识和了解操作系统。

此次对Linux代码的分析有助于了解操作系统的启动,可以更好地理解和认识操作系统是如何管理计算机资源的。

根据课堂所学内容,学生做相应的自主练习,消化课堂所讲解的内容;逐渐增强对linux的深入了解。

1.2任务与分析

本课题主要的目的是了解一个操作系统的初起过程。

根据操作系统的基础知识,分析init/main.c中关于系统初起的相关代码,了解一个操作系统的初起过程,得到相关的框图,写出设计说明书。

1.3Linux内核的特征

1.Linux内核的组织形式为整体式结构。

也就是说整个Linux内核由很多过程组成,每个过程可以独立编译,然后用连接程序将其连接在一起成为一个单独的目标程序。

Linux内核又是开放式的结构,她允许任何人对其进行修正、改进和完善。

2.Linux的进程调度方式简单而有效。

可以说Linux在追求效率方面孜孜不倦,体现在调度方式上也是别具一格。

3.Linux支持内核线程(或称守护进程)。

内核线程是在后台运行而又无终端或登录shell和它结合在一起的进程。

内核线程可以说是用户进程,但和一般的用户进程又有不同,它象内核一样不被换出,因此运行效率较高。

4.Linux支持多种平台的虚拟内存管理。

5.Linux内核另一个独具特色的部分是虚拟文件系统(VFS)。

虚拟文件系统不仅为多种逻辑文件系统(如ext2,fat等)提供了统一的接口,而且为各种硬件设备(作为一种特殊文件)也提供了统一接口。

6.Linux的模块机制使得内核保持独立而又易于扩充。

7.增加系统调用以满足你特殊的需求。

一般来说,系统调用是操作系统的设计者提供给用户使用内核功能的接口,但Linux开放的源代码也允许你设计自己的系统调用,然后把它加入到内核。

8.网络部分面向对象的设计思想使得Linux内核支持多种协议、多种网卡驱动程序变得容易。

 

第二章内核分析

2.1系统内核体系结构

图2.1内核体系结构基本图

图2.2详细的内核体系结构图

2.2代码分析

1)时间结构:

#defineCLOCKS_PER_SEC100/*系统时钟滴答频率,100HZ*/

typedeflongclock_t;/*从进程开始系统经过的时钟滴答数*/

structtm

{

inttm_sec;/*秒数[0,59]*/

inttm_min;/*分钟数[0,59]*/

inttm_hour;/*小时数[0,59]*/

inttm_mday;/*1个月的天数[0,31]*/

inttm_mon;/*1年中月份[0,11]*/

inttm_year;/*从1900年开始的年数*/

inttm_wday;/*1星期中的某天[0,6](星期天=0)*/

inttm_yday;/*1年中的某天[0,365]*/

inttm_isdst;/*夏令时标志*/

};

2)存放硬盘参数表信息:

structdrive_info

{

chardummy[32];

}drive_info;/*用于存放硬盘参数表信息*/

3)tty等待队列数据结构和tty数据结构:

structtty_queue

{

unsignedlongdata;/*等待队列缓冲区中当前数据指针字符数*/

unsignedlonghead;/*缓冲区中数据头指针*/

unsignedlongtail;/*缓冲区中数据尾指针*/

structtask_struct*proc_list;/*等待进程列表*/

charbuf[TTY_BUF_SIZE];/*队列的缓冲区*/

};

structtty_struct     /*tty数据结构*/

{

structtermiostermios;/*终端io属性和控制字符数据结构*/

intpgrp;/*所属进程组*/

intstopped;/*停止标志*/

void(*write)(structtty_struct*tty);/*tty写函数指针*/

structtty_queueread_q;/*tty读队列*/

structtty_queuewrite_q;/*tty写队列*/

structtty_queuesecondary;/*tty辅助队列(存放规范模式字符序列)*/

};/*可称为规范(熟)模式队列*/

1)

2)

3)

4)请求队列中项的结构和块设备结构:

structrequest

/*请求队列中项的结构。

其中如果dev=-1,则表示该项没有被使用*/

{

intdev;/*使用的设备号*/

intcmd;/*命令(READ或WRITE)*/

interrors;/*操作时产生的错误次数*/

unsignedlongsector;/*起始扇区(1块=2扇区)*/

unsignedlongnr_sectors;/*读/写扇区数*/

char*buffer;/*数据缓冲区*/

structtask_struct*waiting;/*任务等待操作执行完成的地方*/

structbuffer_head*bh;/*缓冲区头指针(include/Linux/fs.h,68)*/

structrequest*next;/*指向下一请求项*/

};

structblk_dev_struct        /*块设备结构*/

{void(*request_fn)(void);/*请求操作的函数指针*/

structrequest*current_request;/*请求信息结构*/

};

2.3调用关系图

在内核源代码的init/目录中只有一个main.c文件。

系统在执行完boot/目录中的head.s程序后就会将执行权交给main.c。

该程序虽然不长,但却包括了内核初始化的所有工作。

main.c程序首先利用前面setup.s程序取得的系统参数设置系统的根文件设备号以及一些内存全局变量。

这些内存变量指明了主内存的开始地址、系统所拥有的内存容量和作为高速缓冲区内存的末端地址。

如果还定义了虚拟盘(RAMDISK),则主内存将适当减少。

整个内存的映像示意图见图3.1所示。

图2.3系统中内存功能划分示意图

图中,高速缓冲部分还要扣除被显存和ROMBIOS占用的部分。

高速缓冲区是用于磁盘等块设备临时存放数据的地方,以1K(1024)字节为一个数据块单位。

主内存区域的内存是由内存管理模块mm通过分页机制进行管理分配,以4K字节为一个内存页单位。

内核程序可以自由访问高速缓冲中的数据,但需要通过mm才能使用分配到的内存页面。

然后,内核进行所有方面的硬件初始化工作。

包括陷阱门、块设备、字符设备和tty,包括人工设置第一个任务(task0)。

待所有初始化工作完成后就设置中断允许标志以开启中断,main()也切换到了任务0中运行。

2.3.1staticvoidtime_init(void)分析

该子程用于读取取CMOS时钟,并设置开机时间startup_time(秒)。

structtmtime;

/*时间结构tm定义在include/time.h中*/

do{

time.tm_sec=CMOS_READ(0);/*当前时间秒值(均是BCD码值)*/

time.tm_min=CMOS_READ

(2);/*当前分钟值*/

time.tm_hour=CMOS_READ(4);/*当前小时值*/

time.tm_mday=CMOS_READ(7);/*一月中的当天日期*/

time.tm_mon=CMOS_READ(8);/*当前月份(1—12)*/

time.tm_year=CMOS_READ(9);/*当前年份*/

}while(time.tm_sec!

=CMOS_READ(0));

CMOS的访问速度很慢。

为了减小时间误差,在读取了下面循环中所有数值后,若此时CMOS中秒值发生了变化,那么就重新读取所有值。

BCD_TO_BIN(time.tm_sec);/*转换成二进制数值*/

BCD_TO_BIN(time.tm_min);

BCD_TO_BIN(time.tm_hour);

BCD_TO_BIN(time.tm_mday);

BCD_TO_BIN(time.tm_mon);

BCD_TO_BIN(time.tm_year);

time.tm_mon--;/*tm_mon中月份范围是0—11*/

startup_time=kernel_mktime(&time);

/*调用kernel/mktime.c中函数,计算从1970年1月1日0时起到开机当日经过的秒数,作为开机时间*/

2.3.2voidmain(void)分析

main()函数中完成启动时对设备内核初始化,以及创建进程。

此时中断仍被禁止着,做完必要的设置后就将其开启。

下面这段代码用于保存:

根设备号:

ROOT_DEV;高速缓存末端地址:

buffer_memory_end;机器内存:

memory_end;主内存开始地址:

main_memory_start;

ROOT_DEV=ORIG_ROOT_DEV;/*ROOT_DEV定义在fs/super.c*/

drive_info=DRIVE_INFO;/*复制0x90080处的硬盘参数表*/

memory_end=(1<<20)+(EXT_MEM_K<<10);

/*内存大小=1Mb字节+扩展内存(k)*1024字节*/

memory_end&=0xfffff000;/*忽略不到4Kb(1页)的内存数*/

if(memory_end>16*1024*1024)/*如果内存超过16Mb,则按16Mb计*/

memory_end=16*1024*1024;

if(memory_end>12*1024*1024)/*如果内存>12Mb,则设置缓冲区末端=4Mb*/

buffer_memory_end=4*1024*1024;

elseif(memory_end>6*1024*1024)/*否则如果内存>6Mb,则设置缓冲区末端=2Mb*/

buffer_memory_end=2*1024*1024;

else

buffer_memory_end=1*1024*1024;/*否则则设置缓冲区末端=1Mb*/

main_memory_start=buffer_memory_end;/*主内存起始位置=缓冲区末端*/

/*如果定义了内存虚拟盘,则初始化虚拟盘。

此时主内存将减少。

参见kernel/blk_drv/ramdisk.c。

*/

#ifdefRAMDISK

main_memory_start+=rd_init(main_memory_start,RAMDISK*1024);

#endif

mem_init(main_memory_start,memory_end);/*内核进行所有方面的初始化工作*/

trap_init();/*陷阱门(硬件中断向量)初始化。

(kernel/traps.c)*/

blk_dev_init();/*块设备初始化。

(kernel/blk_drv/ll_rw_blk.c)*/

chr_dev_init();/*字符设备初始化。

(kernel/chr_drv/tty_io.c)*/

tty_init();/*tty初始化。

(kernel/chr_drv/tty_io.c)*/

time_init();/*设置开机启动时间:

startup_time*/

sched_init();/*调度程序初始化(加载了任务0的tr,ldtr)(kernel/sched.c)*/

buffer_init(buffer_memory_end);/*缓冲管理初始化,建内存链表等。

*/

hd_init();/*硬盘初始化。

(kernel/blk_drv/hd.c)*/

floppy_init();/*软驱初始化。

(kernel/blk_drv/floppy.c)*/

sti();/*所有初始化工作都做完了,开启中断*/

/*下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0执行*/

move_to_user_mode();/*移到用户模式下执行。

(include/asm/system.h)*/

if(!

fork()){

init();/*在新建的子进程(任务1)中执行*/

}

图2.2main()流程图

2.3.3pause()分析

代码开始以任务0的身份运行。

对于任何其它的任务,pause()将意味着我们必须等待收到一个信号才会返回就绪运行态,但任务0(task0)是唯一的例外情况,因为任务0在任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0pause()仅意味着我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行pause()。

pause()系统调用(kernel/sched.c,144)会把任务0转换成可中断等待状态,再执行调度函数。

但是调度函数只要发现系统中没有其它任务可以运行时就会切换到任务0,而不依赖于任务0的状态。

2.3.4staticintprintf(constchar*fmt,...)分析

产生格式化信息并输出到标准输出设备stdout

(1),这里是指屏幕上显示。

参数'*fmt'指定输出将采用的格式。

该子程序正好是vsprintf如何使用的一个例子。

该程序使用vsprintf()将格式化的字符串放入printbuf缓冲区,然后用write()将缓冲区的内容输出到标准设备(1--stdout)。

staticintprintf(constchar*fmt,...)

{

va_listargs;

inti;

va_start(args,fmt);

write(1,printbuf,i=vsprintf(printbuf,fmt,args));

va_end(args);

returni;}

2.3.5voidinit(void)分析

argv[0]中的字符“-”是传递给shell程序sh的一个标志。

通过识别该标志,sh程序会作为登录shell执行。

其执行过程与在shell提示符下执行sh不太一样。

staticchar*argv_rc[]={"/bin/sh",NULL};/*调用执行程序时参数的字符串数组*/

staticchar*envp_rc[]={"HOME=/",NULL};/*调用执行程序时的环境字符串数组*/

staticchar*argv[]={"-/bin/sh",NULL};/*同上*/

staticchar*envp[]={"HOME=/usr/root",NULL};

在main()中已经进行了系统初始化,包括内存管理、各种硬件设备和驱动程序。

init()函数运行在任务0第1次创建的子进程(任务1)中。

它首先对第一个将要执行的程序(shell)的环境进行初始化,然后加载该程序并执行之。

setup((void*)&drive_info);

/*这是一个系统调用。

用于读取硬盘参数包括分区表信息并加载虚拟盘(若存在的话)和安装根文件系统设备。

该函数对应函数是sys_setup()*/

然后以读写访问方式打开设备“/dev/tty0”,它对应终端控制台。

由于这是第一次打开文件操作,因此产生的文件句柄号(文件描述符)肯定是0。

该句柄是UNIX类操作系统默认的控制台标准输入句柄stdin。

这里把它以读和写的方式打开是为了复制产生标准输出(写)句柄stdout和标准出错输出句柄stderr。

(void)open("/dev/tty0",O_RDWR,0);

(void)dup(0);/*复制句柄,产生句柄1号--stdout标准输出设备*/

(void)dup(0);/*复制句柄,产生句柄2号--stderr标准出错输出设备*/

打印缓冲区块数和总字节数,每块1024字节主内存区空闲内存字节数。

printf("%dbuffers=%dbytesbufferspace\n\r",NR_BUFFERS,

NR_BUFFERS*BLOCK_SIZE);

printf("Freemem:

%dbytes\n\r",memory_end-main_memory_start);

fork()用于创建一个子进程(任务2)。

对于被创建的子进程,fork()将返回0值,对于原进程(父进程)则返回子进程的进程号pid。

该子进程关闭了句柄0(stdin)、以只读方式打开/etc/rc文件,并使用execve()函数将进程自身替换成/bin/sh程序(即shell程序),然后执行/bin/sh程序。

所带参数和环境变量分别由argv_rc和envp_rc数组给出。

函数_exit()退出时的出错码1–操作未许可;2--文件或目录不存在。

if(!

(pid=fork())){

close(0);

if(open("/etc/rc",O_RDONLY,0))

_exit

(1);/*如果打开文件失败,则退出(lib/_exit.c,10)*/

execve("/bin/sh",argv_rc,envp_rc);/*替换成/bin/sh程序并执行*/

_exit

(2);/*若execve()执行失败则退出*/

}

下面还是父进程

(1)执行的语句。

wait()等待子进程停止或终止,返回值应是子进程的进程号(pid)。

这三句的作用是父进程等待子进程的结束。

&i是存放返回状态信息的位置。

如果wait()返回值不等于子进程号,则继续等待。

if(pid>0)

while(pid!

=wait(&i))/*空循环*/

如果执行到这里,说明刚创建的子进程的执行已停止或终止了。

下面循环中首先再创建一个子进程,如果出错,则显示“初始化程序创建子进程失败”信息并继续执行。

while

(1){

if((pid=fork())<0){

printf("Forkfailedininit\r\n");

continue;

}

if(!

pid){/*新的子进程*/

close(0);close

(1);close

(2);

setsid();/*创建一新的会话*/

(void)open("/dev/tty0",O_RDWR,0);

(void)dup(0);

(void)dup(0);

_exit(execve("/bin/sh",argv,envp));

}

while

(1)

if(pid==wait(&i))

break;

printf("\n\rchild%ddiedwithcode%04x\n\r",pid,i);

sync();/*同步操作,刷新缓冲区*/

}

_exit(0);/*注意!

是_exit(),不是exit()*/

_exit()和exit()都用于正常终止一个函数。

但_exit()直接是一个sys_exit系统调用,而exit()则通常是普通函数库中的一个函数。

它会先执行一些清除操作,例如调用执行各终止处理程序、关闭所有标准IO等,然后调用sys_exit。

第三章配置和编译内核

3.1内核支持(编译)模式

Linux对于计算机硬件、网络和文件系统等部件的驱动程序支持既可以放在系统内核中,也可以作为一个可加载的模块(modules)使用。

当驱动程序放在系统内核中时,Linux假定该硬件是存在于系统中的;而作为可加载模块使用时,只有在知道该硬件存在于系统时才会作为系统内核的一部分,当Linux检测到硬件时,该模块才被加入到系统内核中。

如果把驱动程序编译到内核中,在内核启动时就可以自动支持相应部分的功能,这样做的优点是方便、速度快,机器一旦启动,你就可以使用这部分功能了;缺点是会使内核变得庞大,无论是否需要这部分功能,它都会存在;这可能会增加被系统攻击者利用的漏洞。

一般把经常使用的部分直接编译到内核中,比如网卡等。

如果编译成模块,就会生成对应的.o模块文件,系统启动时它并不在内存中,它是在使用的时候由用户执行insmod命令来动态加载,这样做的优点是不会使内核过分庞大,缺点是你得自己来调用这些模块。

3.2配置系统内核

无论是内核裁减还是内核升级都要重新配置系统内核。

共有三种方式来运行配置内核的命令:

设置屏幕的类型命令

文本makeconfig

窗口菜单(NCurses)makemenuconfig

X图形makexconfig

第1种makeconfig是命令行方式,使用与修改都较为不便,一般不推荐使用。

第2种makemenuconfig是窗口菜单方式,采用窗口菜单进行人机交互,并可随时获得帮助;它占用的内存较少,适合在字符终端下使用,如图3-1所示。

 

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 党团工作 > 入党转正申请

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1