- 新的进程通过复制旧进程(即父进程)而建立。为了创建新进程,在系统物理内存中为新进程创建一个task_struct结构(使用 kmalloc 函数,以便得到一个连续的区域),将旧进程的 task_struct 结构内容复制到其中,再修改部分数据。
- 为新进程分配新的核心堆栈页,分配新的进程标识符 pid。
- 将这个新 task_struct 结构的地址填到task数组中,并调整进程链关系插入运行队列。于是这个新进程便可以在下次度时被选择执行。此时,由于父进程的上下文TSS结构复制到了子进程的 TSS结构中,通过改变其中的部分数据,便可以使子进程执行效果与父一致,都是从系统调用退出。
- 系统调用返回:子进程将得到与父不同的返回值(返回父进程的是子进程的pid,而返回子进程的是 0)。
另外,do_fork()中使用SIGCHLD标志作为参数,该克隆标志在处理后形成exit_signal,将在子进程退出时作为信号发送给父进程。
- 删除已存在用户区域
- 映射私有区域
- 映射共享区域
- 设置当前进程上下文的程序计数器,使之指向程序入口点
见附录三
三、可执行程序的加载
在object文件中有三种主要的类型:
1) 可重定位(relocatable)文件:保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。
2) 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec()如何来创建程序进程映象。
3)共享object文件:保存着代码和合适的数据,用来被链接器链接。被连接编辑器链接时,可以和其他的可重定位和共享object文件来创建其他的object;被动态链接器链接时,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
典型的ELF文件有如下图所示的格式:
当加载器将可执行程序加载到内存中运行时,会根据ELF的段头部表,将可执行文件的代码和数据拷贝到进程的线性空间中,然后跳转到代码的第一条指令处执行。其中,创建可执行文件时,链接器会拷贝一些重定位和符号信息。这样,在执行程序时,加载器会根据.interp节的动态链接器的路径名,加载和运行相应的动态链接器,完成共享库的重定位。之后,将控制传递给应用程序,就可以执行共享库的代码了。通常,linux32的系统中,进程具有如下图所示的地址空间:
代码段中存放:全局常量(const)、字符串常量、函数以及编译时可决定的某些东西
数据段(初始化)中存放:初始化的全局变量、初始化的静态变量(全局的和局部的)
数据段(未初始化)(BSS)中存放:未初始化的全局变量、未初始化的静态变量(全局的和局部的)
堆中存放:动态分配的区域
栈中存放:局部变量(初始化以及未初始化的,但不包含静态变量)、局部常量(const)
附录:
一、linux 3.3x内核中fork()封装的系统调用:
arch/x86/syscalls/systcall_32.tbl
# # 32-bit system call numbers and entry vectors # # The format is: # <number> <abi> <name> <entry point> <compat entry point> # # The abi is always "i386" for this file. # 0 i386 restart_syscall sys_restart_syscall 1 i386 exit sys_exit 2 i386 fork sys_fork stub32_fork
include/linux/syscalls.h
193#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void) 855asmlinkage long sys_fork(void);
kernel/fork.c
1641 1642#ifdef __ARCH_WANT_SYS_FORK 1643SYSCALL_DEFINE0(fork) 1644{ 1645#ifdef CONFIG_MMU 1646 return do_fork(SIGCHLD, 0, 0, NULL, NULL); 1647#else 1648 /* can not support in nommu mode */ 1649 return(-EINVAL); 1650#endif 1651} 1652#endif
二、linux 3.3x内核中exec()封装的系统调用
arch/x86/syscalls/syscall_32.tbl
2011 i386 execve sys_execve stub32_execve
include/linux/syscalls.h
865asmlinkage long sys_execve(const char __user *filename, 866 const char __user *const __user *argv, 867 const char __user *const __user *envp);
arch/x86/kernel/process.c
/* * sys_execve() executes a new program. */ long sys_execve(const char __user *name, const char __user *const __user *argv, const char __user *const __user *envp, struct pt_regs *regs) { long error; char *filename; filename = getname(name); error = PTR_ERR(filename); if (IS_ERR(filename)) return error; error = do_execve(filename, argv, envp, regs); #ifdef CONFIG_X86_32 if (error == 0) { /* Make sure we don't return using sysenter.. */ set_thread_flag(TIF_IRET); } #endif putname(filename); return error; }
三、编程实现shell
1 #include <stdio.h> 2 #include <sys/types.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <sys/wait.h> 6 #include <string.h> 7 int main(){ 8 pid_t pid; 9 char path[20]; 10 while(1){ 11 printf("print path(q to quit):\n"); 12 scanf("%s",path); 13 if(!strcmp(path,"q")) 14 break; 15 pid=fork(); 16 if(pid==0){ 17 execl(path,NULL); 18 printf("child error end\n"); 19 exit(0); 20 } 21 else if(pid>0) 22 wait(NULL); 23 else{ 24 printf("ERROR"); 25 exit(-1); 26 } 27 } 28 }