内核源码: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);
3290

被折叠的 条评论
为什么被折叠?



