内核源码:linux-2.6.38.8.tar.bz2
目标平台:ARM体系结构
无论是在Ubuntu的伪终端(Terminal软件)还是在实终端(如通过串口访问开发板时),无论是直接执行./program命令还是使用shell脚本,它们的实现原理都是当前的shell程序(如bash)首先fork一个子进程,然后子进程调用execve系统调用来完成一个程序的执行(执行program或bash程序)。
$ man 3 exec int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); $ man 2 execve int execve(const char *filename, char *const argv[], char *const envp[]);
前五个库函数只是对execve系统调用的封装而已,它们的关系如下(图片来自《UNIX环境高级编程》):
execve系统调用在Linux内核中的入口点为sys_execve函数。
/* linux-2.6.38.8/arch/arm/kernel/sys_arm.c */ asmlinkage int sys_execve(const char __user *filenamei, const char __user *const __user *argv, const char __user *const __user *envp, struct pt_regs *regs) { int error; char * filename; filename = getname(filenamei); error = PTR_ERR(filename); if (IS_ERR(filename)) goto out; error = do_execve(filename, argv, envp, regs); putname(filename); out: return error; }
其中,filenamei是可执行文件路径名的地址,argv是命令行参数指针数组(最后一个元素为NULL)的地址,envp是环境变量指针数组(最后一个元素也为NULL)的地址。
sys_execve函数把可执行文件路径名拷贝到一个新分配的页面,然后调用do_execve函数,并把这个页面传递给它。
do_execve函数
/* linux-2.6.38.8/fs/exec.c */ int do_execve(const char * filename, const char __user *const __user *argv, const char __user *const __user *envp, struct pt_regs * regs)
1、不使用shell程序所打开的文件(即files_struct结构体)
retval = unshare_files(&displaced); if (retval) goto out_ret;
/* linux-2.6.38.8/kernel/fork.c */ int unshare_files(struct files_struct **displaced) { struct task_struct *task = current; struct files_struct *copy = NULL; int error; error = unshare_fd(CLONE_FILES, ©); if (error || !copy) { *displaced = NULL; return error; } *displaced = task->files; task_lock(task); task->files = copy; task_unlock(task); return 0; } static int unshare_fd(unsigned long unshare_flags, struct files_struct **new_fdp) { struct files_struct *fd = current->files; int error = 0; if ((unshare_flags & CLONE_FILES) && (fd && atomic_read(&fd->count) > 1)) { //用于判断该进程被创建时是否使用父进程的files_struct结构体 *new_fdp = dup_fd(fd, &error); if (!*new_fdp) return error; } return 0; }
2、分配 struct linux_binprm结构体
retval = -ENOMEM; bprm = kzalloc(sizeof(*bprm), GFP_KERNEL); if (!bprm) goto out_files;
linux_binprm结构体用于维护程序执行过程中所使用的各种数据。
/* linux-2.6.38.8/include/linux/binfmts.h */ struct linux_binprm { char buf[BINPRM_BUF_SIZE]; #ifdef CONFIG_MMU struct vm_area_struct *vma; unsigned long vma_pages; #else # define MAX_ARG_PAGES 32 struct page *page[MAX_ARG_PAGES]; #endif struct mm_struct *mm; unsigned long p; /* current top of mem */ unsigned int cred_prepared:1,/* true if creds already prepared (multiple * preps happen for interpreters) */ cap_effective:1;/* true if has elevated effective capabilities, * false if not; except for init which inherits * its parent's caps anyway */ #ifdef __alpha__ unsigned int taso:1; #endif unsigned int recursion_depth; struct file * file; struct cred *cred; /* new credentials */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ unsigned int per_clear; /* bits to clear in current->personality */ int argc, envc; const char * filename; /* Name of binary as seen by procps */ const char * interp; /* Name of the binary really executed. Most of the time same as filename, but could be different for binfmt_{misc,script} */ unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; };
3、为bprm->cred分配struct cred结构体,并复制current->cred中的一些内容
retval = prepare_bprm_creds(bprm); if (retval) goto out_free;
4)、检查struct fs_struct结构体的使用者计数(即current->fs->users)是否超过线程组中所有线程的数量,若是,则标记bprm->unsafe为LSM_UNSAFE_SHARE,若不是,则标记current->fs->in_exec为1。
retval = check_unsafe_exec(bprm); if (retval < 0) goto out_free; clear_in_exec = retval; current->in_execve = 1; //表示当前进程正在执行新程序
5)、获取与可执行文件相关的文件对象(即struct file结构体)
file = open_exec(filename); retval = PTR_ERR(file); if (IS_ERR(file)) goto out_unmark;
/* linux-2.6.38.8/fs/exec.c */ struct file *open_exec(const char *name) { struct file *file; int err; file = do_filp_open(AT_FDCWD, name, O_LARGEFILE | O_RDONLY | __FMODE_EXEC, 0, MAY_EXEC | MAY_OPEN); if (IS_ERR(file)) goto out; err = -EACCES; if (!S_ISREG(file->f_path.dentry->d_inode->i_mode)) //检查是否是普通文件(在Linux中有7种常见的文件类型) goto exit; if (file->f_path.mnt->mnt_flags & MNT_NOEXEC) //检查该文件系统上是否禁止执行可执行文件 goto exit; fsnotify_open(file); err = deny_write_access(file); //检查索引节点(即file->f_path.dentry->d_inode)的i_writecount字段是否大于0, //以确定可执行文件没被写入;把-1存放在这个字段以禁止进一步的写访问 if (err) goto exit; out: return file; exit: fput(file); return ERR_PTR(err); }
6)、在SMP系统上,调用sched_exec函数来确定最小负载CPU以执行新程序,并把当前进程转移过去。
sched_exec();
7)、初始化linux_binprm结构体变量*bprm的file、filename和interp三个字段。
bprm->file = file; bprm->filename = filename; bprm->interp = filename;
8)、分配新的进程地址空间
retval = bprm_mm_init(bprm); if (retval) goto out_file;
/* linux-2.6.38.8/fs/exec.c */ int bprm_mm_init(struct linux_binprm *bprm) { int err; struct mm_struct *mm = NULL; bprm->mm = mm = mm_alloc(); //分配内存描述符 err = -ENOMEM; if (!mm) goto err; err = init_new_context(current, mm);//检查CPU是否支持ASID,若不支持,则返回0 if (err) goto err; err = __bprm_mm_init(bprm); //分配线性区(即struct vm_area_struct结构体) if (err) goto err; return 0; err: if (mm) { bprm->mm = NULL; mmdrop(mm); } return err; }
9)、计算命令行参数和环境变量的个数
bprm->argc = count(argv, MAX_ARG_STRINGS); if ((retval = bprm->argc) < 0) goto out; bprm->envc = count(envp, MAX_ARG_STRINGS); if ((retval = bprm->envc) < 0) goto out;
10)、调用prepare_binprm函数填充linux_binprm结构体变量*bprm的某些字段
retval = prepare_binprm(bprm); if (retval < 0) goto out;
/* linux-2.6.38.8/fs/exec.c */ int prepare_binprm(struct linux_binprm *bprm) { umode_t mode; struct inode * inode = bprm->file->f_path.dentry->d_inode; int retval; mode = inode->i_mode; if (bprm->file->f_op == NULL) return -EACCES; /* clear any previous set[ug]id data from a previous binary */ bprm->cred->euid = current_euid(); //获取当前进程的有效用户ID bprm->cred->egid = current_egid(); //获取当前进程的有效组ID if (!(bprm->file->f_path.mnt->mnt_flags & MNT_NOSUID)) { //MNT_NOSUID表示该文件系统禁止可执行文件设置用户ID /* Set-uid? */ if (mode & S_ISUID) { //检查S_ISUIDD是否置位,若是,则有效用户ID与inode相同 bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->euid = inode->i_uid; } /* Set-gid? */ /* * If setgid is set but no group execute bit then this * is a candidate for mandatory locking, not a setgid * executable. */ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) { //检查S_ISGID是否置位并具有组执行权限, //若是,则有效组ID与inode相同 bprm->per_clear |= PER_CLEAR_ON_SETID; bprm->cred->egid = inode->i_gid; } } /* fill in binprm security blob */ retval = security_bprm_set_creds(bprm); //检查进程权能 if (retval) return retval; bprm->cred_prepared = 1; memset(bprm->buf, 0, BINPRM_BUF_SIZE); //拷贝可执行文件的前128个字节,其中包含可执行文件格式的一个魔数, //魔数用来区分不同的可执行文件格式。 return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE); }
11)、拷贝可执行文件路径名、命令行参数和环境变量
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;
12)、扫描formats链表(当前Linux系统支持的所有可执行文件格式的链表),并不断地尝试调用找到的每种可执行文件格式的load_binary函数(先只是比较魔数而已,不匹配则返回),直到找到匹配的格式为止。
retval = search_binary_handler(bprm,regs); if (retval < 0) goto out;
如果都未找到匹配的可执行文件格式,则返回错误码-ENOEXEC,表示当前的Linux系统不能执行此种格式的程序。
在Linux系统中,常用的可执行文件格式为ELF格式以及使用#!机制的脚本格式。每一种格式都使用struct linux_binfmt结构体来表示。
/* linux-2.6.38.8/include/linux/binfmts.h */ struct linux_binfmt { struct list_head lh; //用于formats构建链表 struct module *module; int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); //用于执行普通程序 int (*load_shlib)(struct file *); //用于加载共享库 int (*core_dump)(struct coredump_params *cprm); //用于程序出错时输出内存转储 unsigned long min_coredump; /* minimal dump size */ };
脚本格式的linux_binfmt实例定义在linux-2.6.38.8/fs/binfmt_script.c文件中。
static struct linux_binfmt script_format = { .module = THIS_MODULE, .load_binary = load_script, };
ELF格式的linux_binfmt实例定义在linux-2.6.38.8/fs/binfmt_elf.c文件中。
static struct linux_binfmt elf_format = { .module = THIS_MODULE, .load_binary = load_elf_binary, .load_shlib = load_elf_library, .core_dump = elf_core_dump, .min_coredump = ELF_EXEC_PAGESIZE, };
其中,load_elf_binary就是用来执行ELF格式可执行文件的函数。
13)、收尾工作
/* execve succeeded */ current->fs->in_exec = 0; current->in_execve = 0; acct_update_integrals(current); free_bprm(bprm); if (displaced) put_files_struct(displaced);