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;
    
  • 相关阅读:
    falsh读取网页数据
    IBATISNET中的lazyLoad
    Json.Net学习笔记(四) Json对象的读写
    Json.Net学习笔记(五) 自定义Json序列化特性
    Json.Net学习笔记(三) Json与Xml之间的转换
    将 GridView 中的内容导出到 Excel 中多个工作表(Sheet) 的方法
    jQuery–20个最新的jQuery效果.
    Json.Net学习笔记(七) 序列化集合
    Json.Net学习笔记(六) Json.Net中关于时间的序列化
    Json.Net 学习笔记(一)
  • 原文地址:https://www.cnblogs.com/wangzahngjun/p/4889816.html
Copyright © 2011-2022 走看看