zoukankan      html  css  js  c++  java
  • 内核中进程的执行流程

    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;
    
  • 相关阅读:
    scrapy中selenium的应用
    Django的锁和事务
    redis
    【leetcode】187. Repeated DNA Sequences
    【leetcode】688. Knight Probability in Chessboard
    【leetcode】576. Out of Boundary Paths
    【leetcode】947. Most Stones Removed with Same Row or Column
    【leetcode】948. Bag of Tokens
    【leetcode】946. Validate Stack Sequences
    【leetcode】945. Minimum Increment to Make Array Unique
  • 原文地址:https://www.cnblogs.com/wangzahngjun/p/4889816.html
Copyright © 2011-2022 走看看