1.新程序的启动
通过用新代码替换现存程序,即可启动新程序。在Linux下这是通过execve系统调用实现的。
该系统调用的入口点是sys_execve函数,然后委托给do_execve函数。其原型如下:
kernel/exec.c
int do_execve(char * filename,
char user * user *argv, char user * user *envp, struct pt_regs * regs)
do_execve的代码流程图:
do_execve()的实现如下所示:
int do_execve(char * filename, char __user *__user *argv,
char __user *__user *envp, struct pt_regs * regs)
{
struct linux_binprm *bprm; /* 保存要执行的文件相关的数据 */
struct file *file;
int retval;
int i;
retval = -ENOMEM;
bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
if (!bprm)
goto out_ret;
file = open_exec(filename); /* 打开要执行的文件,并检查其有效性 */
retval = PTR_ERR(file);
if (IS_ERR(file))
goto out_kfree;
/* 在多处理器系统中才执行,用以分配负载最低的CPU来执行新程序
该函数在include/linux/sched.h文件中被定义如下:
#ifdef CONFIG_SMP
extern void sched_exec(void);
#else
#define sched_exec() {}
#endif
sched_exec(); */
/* 填充linux_binprm结构 */
bprm->p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
bprm->file = file;
bprm->filename = filename;
bprm->interp = filename;
bprm->mm = mm_alloc();
retval = -ENOMEM;
if (!bprm->mm)
goto out_file;
/* 检查当前进程是否在使用LDT,如果是则给新进程分配一个LDT */
retval = init_new_context(current, bprm->mm);
if (retval 0)
goto out_mm;
/* 继续填充linux_binprm结构 */
bprm->argc = count(argv, bprm->p / sizeof(void *));
if ((retval = bprm->argc) 0)
goto out_mm;
bprm->envc = count(envp, bprm->p / sizeof(void *));
if ((retval = bprm->envc) 0)
goto out_mm;
retval = security_bprm_alloc(bprm);
if (retval)
goto out;
/* 检查文件是否可以被执行,填充linux_binprm结构中的e_uid和e_gid项
使用可执行文件的前128个字节来填充linux_binprm结构中的buf项 */
retval = prepare_binprm(bprm);
if (retval 0)
goto out;
/* 将文件名、环境变量和命令行参数拷贝到新分配的页面中 */
retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval 0)
goto out;
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval 0)
goto out;
retval = copy_strings(bprm->argc, argv, bprm);
if (retval 0)
goto out;
//查询能够处理该可执行文件格式的处理函数,并调用相应的load_library方法进行处理
retval = search_binary_handler(bprm,regs);
if (retval >= 0) {
free_arg_pages(bprm);
//执行成功
security_bprm_free(bprm);
acct_update_integrals(current);
kfree(bprm);
return retval;
}
out:
//发生错误,返回inode,并释放资源
for (i = 0 ; i MAX_ARG_PAGES ; i++) {
struct page * page = bprm->page;
if (page)
__free_page(page);
}
if (bprm->security)
security_bprm_free(bprm);
out_mm:
if (bprm->mm)
mmdrop(bprm->mm);
out_file:
if (bprm->file) {
allow_write_access(bprm->file);
fput(bprm->file);
}
out_kfree:
kfree(bprm);
out_ret:
return retval;
}
sys_execve的实现如下所示:
asmlinkage int sys_execve(struct pt_regs regs)
{
int error;
char * filename;
filename = getname((char __user *) regs.ebx); /*将可执行文件的名称装入到一个新分配的页面中 */
error = PTR_ERR(filename);
if (IS_ERR(filename))
goto out;
//执行可执行文件
error = do_execve(filename,
(char __user * __user *) regs.ecx,
(char __user * __user *) regs.edx,
®s);
if (error == 0) {
task_lock(current);
current->ptrace &= ~PT_DTRACE;
task_unlock(current);
set_thread_flag(TIF_IRET);
}
putname(filename);
out:
return error;
}
上述系统调用do_execve的参数在include/asm-i386/ptrace.h中被定义。
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
Linux支持可执行文件的不同格式,标准格式是ELF,但是不同体系架构会使用许多不同的二进制格式,因此二进制格式程序不一定能够在多个架构上运行,二进制格式其实只是表示了可执行文件和内存中程序数据段、代码段等组织情况。
search_binary_handler用于在do_execve结束时查找适当二进制格式,用于所要执行的特定文件,其通常根据文件起始的魔数来识别,二进制格式处理程序则将新程序加载到旧的地址空间中。
二进制格式处理程序执行的操作:
- 释放原进程使用的所有资源;
将应用程序映射到虚拟地址空间。并对下列段进行处理:
a.text包含程序的可执行代码段,start_code和end_code指定该段地址空间中驻留区域;
b.预先初始化的数据位于start_data和end_data,并映射成可执行的对应段;
c.堆用于动态内存分配,边界为start_brk和brk;
d栈由start_stack定义位置;
e.程序参数和环境变量也要映射如虚拟地址空间。列表内容设置进程的指令指针和寄存器。
Linux下程序在内存中的段分布如下图
二进制格式的解释
在Linux内核中任何二进制格式都可用下面的结构体实例表示。
<binfmts.h>
struct linux_binfmt {
struct linux_binfmt * next; struct module *module;
int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(struct file *);
int (*core_dump)(long signr, struct pt_regs * regs, struct file * file); unsigned long min_coredump; /* minimal dump size */
};
其中
1)load_binary用于加载普通程序;
2)oad_shlib:用于加载共享库
3)core_dump:用于在程序错误情况下输出内存转储,随后可进行分析,以解决问题。
每种二进制格式必须使用register_binfmt向内核注册,才能加载
每种二进制格式必须使用register_binfmt向内核注册,才能加载。
1.//linux二进制程序的参数结构体
2.struct linux_binprm{
3. char buf[128]; //128位的缓存
4. unsigned long page[MAX_ARG_PAGES]; //页数组
5. unsigned long p; //
6. int sh_bang; //sh
7. struct inode * inode; //程序点机构体指针
8. int e_uid, e_gid; //程序的uid,gid
9. int argc, envc; //程序的输入参数数和环境参数数
10. char * filename; /* Name of binary */
11.}
2.进程的退出
进程必须使用exit()系统调用退出使得内核能够释放系统资源,该调用的入口点是sys_exit(),sys_exit()需要一个错误码以便退出进程,最后其将工作委托给do_exit()。
该函数的实现主要是将各个引用计数器减1,若减为0无进程在调用,则上交其内存给内存管理模块。