内核中进程的执行流程

1.对内核中do_execve()函数的理解

Linux提供了execle,execlp, execv, execvp和execve等六个用以执行一个可执行文件的函数(其之间的差异在于对命令行参数和环境变量的传递方式不同)。第一个参数是要被执行的程序的路径,第二个参数则向程序传递了命令行参数,第三个参数则向程序传递环境变量。但其本质都是调用sys_execve()中的do_execve()函数。

1.1.do_execve()函数的主要流程:

说明1:do_execve()的参数

int do_execve(char * filename,
	char __user *__user *argv,
	char __user *__user *envp,
	struct pt_regs * regs)

filename:要执行的文件名,argv:参数,envp:指的是环境变量, struct pt_regs:模拟cpu寄存器的地址,其定义如下:

struct pt_regs {
	long ebx;
	long ecx;
	long edx;
	long esi;
	long edi;
	long ebp;
	long eax;
	int  xds;
	int  xes;
	int  xfs;
	int  xgs;
	long orig_eax;
	long eip;
	int  xcs;
	long eflags;
	long esp;
	int  xss;
};

在这个结构体中,记录了用户态下的cpu寄存器在核心态的栈中的保存情况。通过这个参数,sys_execve能获得保存在用户空间的一下信息:课执行文件路径的指针(ebx中),命令行参数的指针(ecx中)和环境变量的指针(edx中)。

1.2. do_execve()函数的准备工作

//不使用shell程序打开文件
retval = unshare_files(&displaced);
	if (retval)
		goto out_ret;

	retval = -ENOMEM;
        //分配  struct  linux_binprm结构体
	bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
	if (!bprm)
		goto out_files;

	retval = prepare_bprm_creds(bprm);
	if (retval)
		goto out_free;

	retval = check_unsafe_exec(bprm);
	if (retval < 0)
		goto out_free;
	clear_in_exec = retval;
	current->in_execve = 1;

这里面最重要的是这个叫struct linux_binpm的结构体,在这个结构体中会保存要执行文件的相关信息。然后会做一些准备工作:prepare_bprm_creds(), 还要进行安全检测:check_unsafe_exec():对要执行的文件进行安全检测,其传入参数是一个linux_binprm的结构体。

struct linux_binprm{
	char buf[BINPRM_BUF_SIZE];  //保存课执行文件的头128个字节
#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;   //命令行参数和环境变量参数
	char * filename;	/* Name of binary as seen by procps */  要被执行的文件的名的二进制
	char * interp;		/* Name of the binary really executed. Most
				   of the time same as filename, but could be
				   different for binfmt_{misc,script} */  要被执行的文件的真是名,通常和filename相同
	unsigned interp_flags;
	unsigned interp_data;
	unsigned long loader, exec;
};

1.3. open_exec()

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))
		goto exit;
        //检查该文件系统上是否禁止执行可执行文件
	if (file->f_path.mnt->mnt_flags & MNT_NOEXEC)
		goto exit;

	fsnotify_open(file->f_path.dentry);
    //检查索引节点(即file->f_path.dentry->d_inode)的i_writecount字段是否大于0,  
	err = deny_write_access(file);
	if (err)
		goto exit;

out:
	return file;

exit:
	fput(file);
	return ERR_PTR(err);
}}

这个函数传入参数为filename,即文件名的字符串,返回值为struct file类型的指针,即返回file结构体,那么这个函数的作用就很清楚了:它会调用do_filp_open()函数,返回这个文件对应的文件对象。(即struct file结构体)。

1.4. shced_exec

得到了struct file结构体,那么该程序执行前,需要先被cpu调度,在这里会调用sched_exec()函数,在这个函数中使用调度类对当前的进程进行调度。

struct task_struct *p = current;
struct rq *rq;
dest_cpu = p->sched_class->select_task_rq(rq, p, SD_BALANCE_EXEC, 0);//调度类

1.5.接着是对bprm结构体的一个填充:bprm_mm_init()函数

	bprm->file = file;
	bprm->filename = filename;
	bprm->interp = filename;

接着会调用bprm_mm_init():对bprm结构体进行初始化,这个函数中主要是初始化了mm_struct

int bprm_mm_init(struct linux_binprm *bprm)
{
	int err;
	struct mm_struct *mm = NULL;
        //为mm_struct分配内存
	bprm->mm = mm = mm_alloc();
	err = -ENOMEM;
	if (!mm)
		goto err;
        //初始化进程上下文
	err = init_new_context(current, mm);
	if (err)
		goto err;
        //初始化mm_struct
	err = __bprm_mm_init(bprm);
	if (err)
		goto err;

	return 0;

err:
	if (mm) {
		bprm->mm = NULL;
		mmdrop(mm);
	}

	return err;
}

下面是继续对bprm结构体填充的一个过程

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;

紧接着会调用prepare_binprm()函数,填充Linux_binprm结构中的e_uid项和e_gid项。并且使用可执行文件的前128个字节来填充linux_binprm结构中的Buf项。

int prepare_binprm(struct linux_binprm *bprm)
{
        //.............................
    	bprm->cred->euid = current_euid();
	bprm->cred->egid = current_egid();
        //..................................
        memset(bprm->buf, 0, BINPRM_BUF_SIZE);
	return kernel_read(bprm->file, 0, bprm->buf, BINPRM_BUF_SIZE);
}

接着将文件名,环境变量和命令行参数等拷贝到新分配的页面中

	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;

1.6.将可执行的二进制文件load进来,search_binary_handler()函数

在这个函数中涉及到一个结构体struct linux_binfmt,在这个结构体中有一个函数指针,负责将要执行的二进制程序load进来。

struct linux_binfmt {
	struct list_head lh;
	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 limit);
	unsigned long min_coredump;	/* minimal dump size */
	int hasvdso;
};
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
    /................/
for (try=0; try<2; try++) {
        read_lock(&binfmt_lock);
        list_for_each_entry(fmt, &formats, lh) {
            int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
            if (!fn)
                continue;
            if (!try_module_get(fmt->module))
                continue;
            read_unlock(&binfmt_lock);
//具体的执行的函数
            retval = fn(bprm, regs);
            /*
             * Restore the depth counter to its starting value
             * in this call, so we don't have to rely on every
             * load_binary function to restore it on return.
             */
            bprm->recursion_depth = depth;
            if (retval >= 0) {
                if (depth == 0)
                    tracehook_report_exec(fmt, bprm, regs);
                put_binfmt(fmt);
                allow_write_access(bprm->file);
                if (bprm->file)
                    fput(bprm->file);
                bprm->file = NULL;
                current->did_exec = 1;
                proc_exec_connector(current);
                return retval;
            }
            read_lock(&binfmt_lock);
            put_binfmt(fmt);
            if (retval != -ENOEXEC || bprm->mm == NULL)
                break;
            if (!bprm->file) {
                read_unlock(&binfmt_lock);
                return retval;
            }
        }
        read_unlock(&binfmt_lock);
        if (retval != -ENOEXEC || bprm->mm == NULL) {
            break;
        }
    }
    return retval;
}
}

1、外层是一个循环,for (try=0; try<2; try++) 貌似是重试两次的意思,具体为什么是两次,暂时还没搞明白.
2、里层通过list_for_each_entry(fmt, &formats, lh)遍历二进制格式列表,寻找合适的handler,找到之后,让fn函数去执行。

1.7.收尾工作

当执行完之后,会有一些收尾工作:释放掉bprm结构体等。

	/* execve succeeded */
	current->fs->in_exec = 0;
	current->in_execve = 0;
	acct_update_integrals(current);
	free_bprm(bprm);
	if (displaced)
		put_files_struct(displaced);
	return retval;
posted @ 2015-10-18 17:05  wangLinuxer  阅读(1511)  评论(0编辑  收藏  举报