20169210《Linux内核原理与分析》第九周作业

第一部分:实验
首先还是网易云课堂的学习,这次的课程是可执行程序的装载。

预处理、编译和链接:

可执行程序是怎么来的
以c语言代码为例的话,经过预处理,编译成汇编代码,再汇编成目标码再链接可执行文件。

过程如图所示,.c用gcc编译成汇编代码.asm,然后再汇编成目标码.o再ld链接成可执行文件。
以helloworld为例,处理过程如下所示

vi hello.c                                          
gcc -E -o hello.cpp hello.c -m32               //对.c文件预处理,hello.cpp是预处理的中间文件,预处理负责把include的文件包含进来及宏替换等工作     
vi hello.cpp                                        
gcc -x cpp-output -S -o hello.s hello.cpp -m32 //编译成汇编代码.s    
vi hello.s                                          
gcc -x assembler -c hello.s -o hello.o -m32    //汇编成目标代码.o是二进制的文件    
vi hello.o                                       
gcc -o hello hello.o -m32                      //链接成可执行文件     
vi hello                                            
gcc -o hello.static hello.o -m32 -static       //hello就是可执行文件,也是二进制的,hello.o和hello都是ELF格式的文件,这样编译出来的可执行文件使用的

                                               //是共享库,加上-static就是静态编译,就是把所有的程序依赖的东西都放到程序内部     
使用gdb跟踪sys_execve内核函数的处理过程:

首先是搭载环境,在实验楼中做实验,用test_exec.c覆盖test.c的内容,但是无法克隆,所以只能将test.c的内容修改成test_exec.c的内容,修改部分代码如下

#include <time.h>
#include <unistd.h> 

int Time(int argc, char *argv[])
{
    time_t tt;
    struct tm *t;
    tt = time(NULL);
    t = localtime(&tt);
    printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
    return 0;
}

int TimeAsm(int argc, char *argv[])
{
    time_t tt;
    struct tm *t;
    asm volatile(
        "mov $0,%%ebx\n\t"
        "mov $0xd,%%eax\n\t" 
        "int $0x80\n\t" 
        "mov %%eax,%0\n\t"  
        : "=m" (tt) 
    );
    t = localtime(&tt);
    printf("time:%d:%d:%d:%d:%d:%d\n",t->tm_year+1900, t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
    return 0;
}

int Fork(int argc, char *argv[])
{
	int pid;
	/* fork another process */
	pid = fork();
	if (pid < 0) 
	{ 
		/* error occurred */
		fprintf(stderr,"Fork Failed!");
		exit(-1);
	} 
	else if (pid == 0) 
	{
		/*	 child process 	*/
    	printf("This is Child Process!\n");
	} 
	else 
	{ 	
		/* 	parent process	 */
    	printf("This is Parent Process!\n");
		/* parent will wait for the child to complete*/
		wait(NULL);
		printf("Child Complete!\n");
	}
}

int Exec(int argc, char *argv[])
{
	int pid;
	/* fork another process */
	pid = fork();
	if (pid < 0) 
	{ 
		/* error occurred */
		fprintf(stderr,"Fork Failed!");
		exit(-1);
	} 
	else if (pid == 0) 
	{
		/*	 child process 	*/
    	printf("This is Child Process!\n");
		execlp("/hello","hello",NULL);
	} 
	else 
	{ 	
		/* 	parent process	 */
    	printf("This is Parent Process!\n");
		/* parent will wait for the child to complete*/
		wait(NULL);
		printf("Child Complete!\n");
	}
}

    MenuConfig("time","Show System Time",Time);
    MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
    MenuConfig("fork","Fork a new process",Fork);
    MenuConfig("exec","Execute a program",Exec);

对于Exec()函数跟fork()的代码是类似的,只是在子进程里面增加了execlp(),启动的是hello,就是写的hello world。
接着是创建hello.c文件,如下图所示

menu中的Makefile也有修改的内容,如下图所示

用静态编译的方式编译了hello.c,然后在生成根文件系统时把init和hello都放到rootfs.img里面了,这样在执行exec时就可以自动加载hello这个可执行文件。
程序的执行结果如下

我们可以看到exec命令,执行exec发现比fork命令多了的hello world,实际上这个hello world是执行新加载的程序输出的。
接下来就是使用gdb跟踪。使用-S和-s,命令如下

qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

接下来就是使用gdb设置断点跟踪,设置的断点如下图所示

连续输入三个c启动MenuOS,如下图所示

在MenuOS中输入exec命令就会停在断点sys_execve处,如下图所示


输入c继续执行,停在了第二个断点处,list查看函数,如下图所示

输入c继续执行,来到stard_thread,如下图所示

使用po new_ip查看new_ip的指向,

其实new_ip是返回到用户态的第一条指令的地址。

我们可以看到new_ip的地址就是入口地址。
输入s进入到内部

我们可以看到在修改内核堆栈的位置,把regs_ip修改成hello的起点,这样再返回用户态时就有了新的执行环境。

第二部分:教材

虚拟文件系统中(VFS)有四个主要的对象类型
  • 超级块对象:代表一个具体的已安装文件系统;
  • 索引节点对象:代表一个具体文件;
  • 目录项对象:代表一个目录项,是路径的一个组成部分;
  • 文件对象:代表由进程打开的文件

扇区是设备的最小寻址单元,块是文件的最小寻址单元。

四种I/O调度程序:
  • Linus电梯。能执行合并与排序预处理;
  • 最终期限I/O调度程序。为解决请求饥饿问题;
  • 完全公正的排队I/O调度程序。以时间片轮转调度队列;
  • 空操作的I/O调度程序。专为随机访问设备而设计的。
posted @ 2016-12-01 21:04  刘翠杰  阅读(116)  评论(0编辑  收藏  举报