第一部分:实验
首先还是网易云课堂的学习,这次的课程是可执行程序的装载。
预处理、编译和链接:
可执行程序是怎么来的
以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调度程序。专为随机访问设备而设计的。