ID **超 学号:SA*****256
实验内容:
1.参考进程初探 编程实现fork(创建一个进程实体) -> exec(将ELF可执行文件内容加载到进程实体) -> running program
2.参照C代码中嵌入汇编代码示例及用汇编代码使用系统调用time示例分析fork和exec系统调用在内核中的执行过程
3.注意task_struct进程控制块,ELF文件格式与进程地址空间的联系,注意Exec系统调用返回到用户态时EIP指向的位置。
4.动态链接库在ELF文件格式中与进程地址空间中的表现形式
实验目的:加深理解Linux工作原理
实验环境:Ubuntu12.10 内核版本3.5.0-17-generic
实验分析及过程:
第一部分:fork() 和 exec()
在linux中,有三种方式可以启动新进程
1.使用system调用
#include<stdlib.h> int system(const char* string);它的作用是,运行以字符串参数的形式传递给它的命令并等待该命令的完成。命令的执行情况如同在shell中执行如下命令:
$ sh -c string我们来看下面的一个例子吧:
#include<stdlib.h> #include<stdio.h> int main() { printf("Running ps with system call\n"); system("ps aux"); printf("system call Done\n"); exit(0); }
在这里,我们相当于在shell中 输入了 ps aux 命令
2.使用fork()
#include<sys/types.h> #include<unistd.h> pid_t fork(void);
这个系统调用复制当前进程,在进程表中创建一个新的表项,新表项中的许多属性与当前进程相同。但是新进程有自己的数据空间(堆和栈),环境和文件描述符。在父进程中的fork调用返回的是新的子进程的PID,而新进程返回的是0.程序代码也靠这一点来区分父子进程。创建失败返回-1.这边在之前看到过有这么一个解释,相当与是一个链状的进程序列,子进程没有儿子了,所以0相当于指向为空,是不是和链表结构很像呢?具体的linux是怎么实现的,待我之后考证。
说了这么多,我们来看下面的例子来加深下理解吧。
#include<sys/types.h> #include<unistd.h> #include<stdlib.h> #include<stdio.h> int main() { pid_t pid; pid=fork(); if(0==pid) { pid_t cpid=getpid(); printf("this is the child thread,cpid=%d\n",cpid); } else if(pid>0) { pid_t ppid=getpid(); printf("this is the parent thread,ppid=%d\n",ppid); } else printf("fork error\n"); return 0; }
3.使用exec()
exec() 系列函数有一组相关的函数组成 ,exec函数可以把当前进程替换为另一个新进程,新进程由path 或者file 参数指定。我们可以使用exec函数将程序的执行从一个程序切换到另一个程序。在新的程序启动后,原来的程序就不再运行了。
#include<unistd.h> char ** environ; int execl(const char *path,const char *arg0,...,(char *)0); int execlp(const char *file,const char *arg0,...,(char *)0); int execle(const char *path,const char *arg0,...,(char *)0,char *const envp[]); int execv(const char *path,char *const argv[]); int execvp(const char *file,char *const argv[]); int execve(const char *path,char *const argv[],char *const envp[]);这些函数可以分为两大类。execl execlp execle 的参数个数可以变化,参数以一个空指针结束。execv execvp 的第二个参数是一个字符串数组。不管哪种情况,新程序在启动时会把argv数组中给定的参数传递给main函数。
这些函数通常都是用execve实现的。我们来看下面的一个例子,在这个例子当中,我直接指定了各个变量,并没有从shell中读入。
#include<stdlib.h> #include<unistd.h> #include<stdio.h> int main(int argc,char *argv[],char *envp[]) { pid_t pre_pid=getpid(); printf("Before Running ps with execlp,pre_pid=%d\n",pre_pid); execlp("ps","ps","-l",0); pid_t after_pid=getpid(); printf("After Runing ps wiht execlp,after_pid=%d\n",after_pid); exit(0); }
我们发现一个很有趣的现象,
printf("After Runing ps wiht execlp,after_pid=%d\n",after_pid);
并没有被执行。这是由于exec函数取代了原先的进程,一般情况下,exec函数是不会返回的,除非发生错误。出现错误时,exec函数返回-1,并设置错误变量errno。
特别要注意的一点,在原进程中已打开的文件描述符在新进程中仍将保持打开,除非它们的执行时关闭标志被置位。
第二部分:fork和exec系统调用在内核中的执行过程
对fork函数进行反汇编
汇编的时候要注意设置断点
如下:
gcc -g forktest.c -o forktest gdb forktest b fork r disas fork
对exec函数进行反汇编