码迷,mamicode.com
首页 > 系统相关 > 详细

Linux内核源代码情景分析-execve()

时间:2015-03-20 20:33:32      阅读:197      评论:0      收藏:0      [点我收藏+]

标签:

    子进程开始执行execve:

    execve("/bin/echo", args, NULL});

    系统调用execve内核入口是sys_execve,代码如下:

asmlinkage int sys_execve(struct pt_regs regs)
{
	int error;
	char * filename;

	filename = getname((char *) regs.ebx);//ebx为"/bin/echo",把字符串从用户空间拷贝到系统空间
	error = PTR_ERR(filename);
	if (IS_ERR(filename))
		goto out;
	error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, &regs);//ecx为args,edx为NULL
	if (error == 0)
		current->ptrace &= ~PT_DTRACE;
	putname(filename);
out:
	return error;
}
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
	struct linux_binprm bprm;
	struct file *file;
	int retval;
	int i;

	file = open_exec(filename);//打开目标文件 /bin/echo

	retval = PTR_ERR(file);
	if (IS_ERR(file))
		return retval;

	bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);//1024*32减去一个指针的大小
	memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); //page指针数组初始化为0

	bprm.file = file;
	bprm.filename = filename;
	bprm.sh_bang = 0;
	bprm.loader = 0;
	bprm.exec = 0;
	if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {//对指针数组argv[]中参数的个数进行计数,而bprm.p / sizeof(void *)表示允许的最大值,由于agrv[]是在用户空间而不在系统空间	
		allow_write_access(file);
		fput(file);
		return bprm.argc;
	}

	if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {//同上
		allow_write_access(file);
		fput(file);
		return bprm.envc;
	}

	retval = prepare_binprm(&bprm);//见下面的代码
	if (retval < 0) 
		goto out; 

	retval = copy_strings_kernel(1, &bprm.filename, &bprm);//由于bprm.filename已经在系统空间了,所以copy_strings_kernel从系统空间中拷贝
	if (retval < 0) 
		goto out; 

	bprm.exec = bprm.p;
	retval = copy_strings(bprm.envc, envp, &bprm);//从用户空间拷贝,参考copy_strings_kernel
	if (retval < 0) 
		goto out; 

	retval = copy_strings(bprm.argc, argv, &bprm);//从用户空间拷贝,参考copy_strings_kernel
	if (retval < 0) 
		goto out; 

	retval = search_binary_handler(&bprm,regs);//已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它
	if (retval >= 0)
		/* execve success */
		return retval;

out:
	/* Something went wrong, return the inode and free the argument pages*/
	allow_write_access(bprm.file);
	if (bprm.file)
		fput(bprm.file);

	for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
		struct page * page = bprm.page[i];
		if (page)
			__free_page(page);
	}

	return retval;
}

struct linux_binprm{
	char buf[BINPRM_BUF_SIZE];//128
	struct page *page[MAX_ARG_PAGES];//32
	unsigned long p; /* current top of mem */
	int sh_bang;
	struct file * file;
	int e_uid, e_gid;
	kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
	int argc, envc;
	char * filename;	/* Name of binary */
	unsigned long loader, exec;
};
int prepare_binprm(struct linux_binprm *bprm)
{
	int mode;
	struct inode * inode = bprm->file->f_dentry->d_inode;

	mode = inode->i_mode;
	/* Huh? We had already checked for MAY_EXEC, WTF do we check this? */
	if (!(mode & 0111))	/* with at least _one_ execute bit set */
		return -EACCES;
	if (bprm->file->f_op == NULL)
		return -EACCES;

	bprm->e_uid = current->euid;
	bprm->e_gid = current->egid;

	if(!IS_NOSUID(inode)) {
		/* Set-uid? */
		if (mode & S_ISUID)
			bprm->e_uid = 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))
			bprm->e_gid = inode->i_gid;
	}

	/* We don‘t have VFS support for capabilities yet */
	cap_clear(bprm->cap_inheritable);
	cap_clear(bprm->cap_permitted);
	cap_clear(bprm->cap_effective);

	/*  To support inheritance of root-permissions and suid-root
         *  executables under compatibility mode, we raise all three
         *  capability sets for the file.
         *
         *  If only the real uid is 0, we only raise the inheritable
         *  and permitted sets of the executable file.
         */

	if (!issecure(SECURE_NOROOT)) {
		if (bprm->e_uid == 0 || current->uid == 0) {
			cap_set_full(bprm->cap_inheritable);
			cap_set_full(bprm->cap_permitted);
		}
		if (bprm->e_uid == 0) 
			cap_set_full(bprm->cap_effective);
	}

	memset(bprm->buf,0,BINPRM_BUF_SIZE);//从可执行文件中读入开头的128个字节到linux_binprm结构bprm中的缓冲区
	return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
}


    copy_strings_kernel从系统空间拷贝,代码如下:

int copy_strings_kernel(int argc,char ** argv, struct linux_binprm *bprm)
{
	int r;
	mm_segment_t oldfs = get_fs();
	set_fs(KERNEL_DS); 
	r = copy_strings(argc, argv, bprm);
	set_fs(oldfs);
	return r; 
}
int copy_strings(int argc,char ** argv, struct linux_binprm *bprm) 
{
	while (argc-- > 0) {//参数的个数
		char *str;
		int len;
		unsigned long pos;

		if (get_user(str, argv+argc) || !str || !(len = strnlen_user(str, bprm->p))) 
			return -EFAULT;
		if (bprm->p < len) 
			return -E2BIG; 

		bprm->p -= len;//堆栈的指针再减去参数的长度
		/* XXX: add architecture specific overflow check here. */ 

		pos = bprm->p;
		while (len > 0) {
			char *kaddr;
			int i, new, err;
			struct page *page;
			int offset, bytes_to_copy;

			offset = pos % PAGE_SIZE;//offset为在本页中的偏移
			i = pos/PAGE_SIZE;//i为31,最后一页
			page = bprm->page[i];
			new = 0;
			if (!page) {
				page = alloc_page(GFP_HIGHUSER);//分配页面
				bprm->page[i] = page;
				if (!page)
					return -ENOMEM;
				new = 1;
			}
			kaddr = kmap(page);//得到页面低端地址

			if (new && offset)//新分配的页面
				memset(kaddr, 0, offset);//从页面低端地址到(页面低端地址 + offset)都清0
			bytes_to_copy = PAGE_SIZE - offset;
			if (bytes_to_copy > len) {
				bytes_to_copy = len;
				if (new)
					memset(kaddr+offset+len, 0, PAGE_SIZE-offset-len);
			}
			err = copy_from_user(kaddr + offset, str, bytes_to_copy);//从kaddr + offset到(kaddr + offset + len)赋值为str
			kunmap(page);

			if (err)
				return -EFAULT; 

			pos += bytes_to_copy;
			str += bytes_to_copy;
			len -= bytes_to_copy;
		}
	}
	return 0;
}
    整个的执行图如下:

    技术分享

    search_binary_handler,已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它,代码如下:

int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
	int try,retval=0;
	struct linux_binfmt *fmt;
        ......
	for (try=0; try<2; try++) {
		read_lock(&binfmt_lock);
		for (fmt = formats ; fmt ; fmt = fmt->next) {
			int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
			if (!fn)
				continue;
			if (!try_inc_mod_count(fmt->module))
				continue;
			read_unlock(&binfmt_lock);
			retval = fn(bprm, regs);//返回-ENOEXEC表示对不上号
			if (retval >= 0) {
				put_binfmt(fmt);
				allow_write_access(bprm->file);
				if (bprm->file)
					fput(bprm->file);
				bprm->file = NULL;
				current->did_exec = 1;
				return retval;
			}
			read_lock(&binfmt_lock);
			put_binfmt(fmt);
			if (retval != -ENOEXEC)//只要不是对不上号,就退出循环
				break;
			if (!bprm->file) {
				read_unlock(&binfmt_lock);
				return retval;
			}
		}
		read_unlock(&binfmt_lock);
		if (retval != -ENOEXEC) {
			break;
#ifdef CONFIG_KMOD
		}else{
#define printable(c) (((c)==‘\t‘) || ((c)==‘\n‘) || (0x20<=(c) && (c)<=0x7e))
			char modname[20];
			if (printable(bprm->buf[0]) &&
			    printable(bprm->buf[1]) &&
			    printable(bprm->buf[2]) &&
			    printable(bprm->buf[3]))
				break; /* -ENOEXEC */
			sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
			request_module(modname);//试着将相应的模块装入,一共进行两次循环,正是为了在安装了模块以后再来试一次
#endif
		}
	}
	return retval;
}
   

    我们假设/bin/echo是a.out格式。

static struct linux_binfmt aout_format = {
	NULL, THIS_MODULE, load_aout_binary, load_aout_library, aout_core_dump, PAGE_SIZE
};
    所以fn(bprm, regs),执行的代码是load_aout_binary。
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
	struct exec ex;
	unsigned long error;
	unsigned long fd_offset;
	unsigned long rlim;
	int retval;

	ex = *((struct exec *) bprm->buf);		//128字节可执行文件头部
	if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC &&
	     N_MAGIC(ex) != QMAGIC && N_MAGIC(ex) != NMAGIC) ||
	    N_TRSIZE(ex) || N_DRSIZE(ex) ||
	    bprm->file->f_dentry->d_inode->i_size < ex.a_text+ex.a_data+N_SYMSIZE(ex)+N_TXTOFF(ex)) {
		return -ENOEXEC;//匹配不会返回-ENOEXEC
	}

	fd_offset = N_TXTOFF(ex);//根据代码的特性取得代码段在目标文件中的起始位置

	/* Check initial limits. This avoids letting people circumvent
	 * size limits imposed on them by creating programs with large
	 * arrays in the data or bss.
	 */
	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
	if (rlim >= RLIM_INFINITY)
		rlim = ~0;
	if (ex.a_data + ex.a_bss > rlim)//代码段和bss段的总和不能超过限制
		return -ENOMEM;

	/* Flush all traces of the currently running executable */
	retval = flush_old_exec(bprm);//到了"与过去告别"的时候了,这种"告别过去"意味着放弃从父进程"继承"下来的全部用户空间,不管是通过复制还是通过指针共享继承下来的
	if (retval)
		return retval;

	/* OK, This is the point of no return */
#if !defined(__sparc__)
	set_personality(PER_LINUX);
#else
	set_personality(PER_SUNOS);
#if !defined(__sparc_v9__)
	memcpy(current->thread.core_exec, &ex, sizeof(struct exec));
#endif
#endif

	current->mm->end_code = ex.a_text +
		(current->mm->start_code = N_TXTADDR(ex));//代码段起始和结束地址,详细请看紧邻本代码段的相关代码
	current->mm->end_data = ex.a_data +
		(current->mm->start_data = N_DATADDR(ex));//数据段起始和结束地址,数据段起始地址就是代码段结束地址
	current->mm->brk = ex.a_bss +
		(current->mm->start_brk = N_BSSADDR(ex));//bss段起始和结束地址,bss起始地址就是数据段结束地址

	current->mm->rss = 0;
	current->mm->mmap = NULL;
	compute_creds(bprm);
 	current->flags &= ~PF_FORKNOEXEC;
#ifdef __sparc__
	if (N_MAGIC(ex) == NMAGIC) {
		loff_t pos = fd_offset;
		/* Fuck me plenty... */
		/* <AOL></AOL> */
		error = do_brk(N_TXTADDR(ex), ex.a_text);
		bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
			  ex.a_text, &pos);
		error = do_brk(N_DATADDR(ex), ex.a_data);
		bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
			  ex.a_data, &pos);
		goto beyond_if;
	}
#endif

	if (N_MAGIC(ex) == OMAGIC) {//如果魔术是OMAGIC
		unsigned long text_addr, map_size;
		loff_t pos;

		text_addr = N_TXTADDR(ex);//代码段起始地址

#if defined(__alpha__) || defined(__sparc__)
		pos = fd_offset;//根据代码的特性取得代码段在目标文件中的起始位置
		map_size = ex.a_text+ex.a_data + PAGE_SIZE - 1;//代码段和数据段的大小
#else
		pos = 32;
		map_size = ex.a_text+ex.a_data;
#endif

		error = do_brk(text_addr & PAGE_MASK, map_size);//为正文段和数据段合在一起分配空间
		if (error != (text_addr & PAGE_MASK)) {
			send_sig(SIGKILL, current, 0);
			return error;
		}

		error = bprm->file->f_op->read(bprm->file, (char *)text_addr,
			  ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
		if (error < 0) {
			send_sig(SIGKILL, current, 0);
			return error;
		}
			 
		flush_icache_range(text_addr, text_addr+ex.a_text+ex.a_data);
	} else {//如果魔术不是OMAGIC
		static unsigned long error_time, error_time2;
		if ((ex.a_text & 0xfff || ex.a_data & 0xfff) &&
		    (N_MAGIC(ex) != NMAGIC) && (jiffies-error_time2) > 5*HZ)
		{
			printk(KERN_NOTICE "executable not page aligned\n");
			error_time2 = jiffies;
		}

		if ((fd_offset & ~PAGE_MASK) != 0 &&
		    (jiffies-error_time) > 5*HZ)
		{
			printk(KERN_WARNING 
			       "fd_offset is not page aligned. Please convert program: %s\n",
			       bprm->file->f_dentry->d_name.name);
			error_time = jiffies;
		}

		if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {//如果没有mmap函数或者代码段及数据段的长度不与页面大小对齐
			loff_t pos = fd_offset;
			do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);//和上面的方式一样,正文段和数据段合在一起分配空间
			bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
					ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
			flush_icache_range((unsigned long) N_TXTADDR(ex),
					   (unsigned long) N_TXTADDR(ex) +
					   ex.a_text+ex.a_data);
			goto beyond_if;
		}

		down(current->mm->mmap_sem);
		error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text,//如果有mmap,将文件的代码段映射到进程的用户空间
			PROT_READ | PROT_EXEC,
			MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
			fd_offset);
		up(current->mm->mmap_sem);

		if (error != N_TXTADDR(ex)) {
			send_sig(SIGKILL, current, 0);
			return error;
		}

		down(current->mm->mmap_sem);
 		error = do_mmap(bprm->file, N_DATADDR(ex), ex.a_data,///如果有mmap,将文件的数据段映射到进程的用户空间
				PROT_READ | PROT_WRITE | PROT_EXEC,
				MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
				fd_offset + ex.a_text);//数据段在文件中的偏移是fd_offset + ex.a_text
		up(current->mm->mmap_sem);
		if (error != N_DATADDR(ex)) {
			send_sig(SIGKILL, current, 0);
			return error;
		}
	}
beyond_if:
	set_binfmt(&aout_format);//current->binfmt = aout_format;

	set_brk(current->mm->start_brk, current->mm->brk);//为可执行文件的bss段分配空间并建立起页面映射

	retval = setup_arg_pages(bprm); //在用户空间的堆栈区顶部为进程建立起一个虚拟区间,并将执行参数以及环境变量所占的物理页面与此虚拟区间建立起映射
	if (retval < 0) { 
		/* Someone check-me: is this error path enough? */ 
		send_sig(SIGKILL, current, 0); 
		return retval;
	}

	current->mm->start_stack =
		(unsigned long) create_aout_tables((char *) bprm->p, bprm);//把一些变量放入用户堆栈中,返回的是STACK_TOP减去图中参数所占空间,也就是堆栈开始的虚拟地址
#ifdef __alpha__
	regs->gp = ex.a_gpvalue;
#endif
	start_thread(regs, ex.a_entry, current->mm->start_stack);//设置系统堆栈中的eip,esp
	if (current->ptrace & PT_PTRACED)
		send_sig(SIGTRAP, current, 0);
	return 0;
}
struct exec
{
  unsigned long a_info;		/* Use macros N_MAGIC, etc for access */
  unsigned a_text;		/* length of text, in bytes */
  unsigned a_data;		/* length of data, in bytes */
  unsigned a_bss;		/* length of uninitialized data area for file, in bytes */
  unsigned a_syms;		/* length of symbol table data in file, in bytes */
  unsigned a_entry;		/* start address */
  unsigned a_trsize;		/* length of relocation info for text, in bytes */
  unsigned a_drsize;		/* length of relocation info for data, in bytes */
};
#define _N_HDROFF(x) (1024 - sizeof (struct exec))

#if !defined (N_TXTOFF)
#define N_TXTOFF(x)  (N_MAGIC(x) == ZMAGIC ? _N_HDROFF((x)) + sizeof (struct exec) :   (N_MAGIC(x) == QMAGIC ? 0 : sizeof (struct exec)))
#endif
#if !defined (N_TXTADDR)
#define N_TXTADDR(x) (N_MAGIC(x) == QMAGIC ? PAGE_SIZE : 0)
#endif
#define _N_TXTENDADDR(x) (N_TXTADDR(x)+(x).a_text)

#ifndef N_DATADDR
#define N_DATADDR(x)     (N_MAGIC(x)==OMAGIC? (_N_TXTENDADDR(x))      : (_N_SEGMENT_ROUND (_N_TXTENDADDR(x))))
#endif

#if !defined (N_BSSADDR)
#define N_BSSADDR(x) (N_DATADDR(x) + (x).a_data)
#endif

    

    flush_old_exec,到了"与过去告别"的时候了,这种"告别过去"意味着放弃从父进程"继承"下来的全部用户空间,不管是通过复制还是通过共享继承下来的,代码如下:

int flush_old_exec(struct linux_binprm * bprm)
{
	char * name;
	int i, ch, retval;
	struct signal_struct * oldsig;

	/*
	 * Make sure we have a private signal table
	 */
	oldsig = current->sig;
	retval = make_private_signals();//如果子进程通过指针来共享父进程的信号处理表,就要把它复制过来
	if (retval) goto flush_failed;

	/* 
	 * Release all of the old mmap stuff
	 */
	retval = exec_mmap();//从父进程继承下来的用户空间就是在这里放弃的
	if (retval) goto mmap_failed;

	/* This is the point of no return */
	release_old_signals(oldsig);//如果子进程通过指针来共享父进程的信号处理表,那么就要减少oldsig->count的计数

	current->sas_ss_sp = current->sas_ss_size = 0;

	if (current->euid == current->uid && current->egid == current->gid)
		current->dumpable = 1;
	name = bprm->filename;
	for (i=0; (ch = *(name++)) != ‘\0‘;) {
		if (ch == ‘/‘)
			i = 0;
		else
			if (i < 15)
				current->comm[i++] = ch;
	}
	current->comm[i] = ‘\0‘;//comm[],用于保存进程所执行的程序名,所以还要把bprm->filename的目标程序路径名的最后一段抄过去			

	flush_thread();

	de_thread(current);//从线程组中脱离出来

	if (bprm->e_uid != current->euid || bprm->e_gid != current->egid || 
	    permission(bprm->file->f_dentry->d_inode,MAY_READ))
		current->dumpable = 0;

	/* An exec changes our domain. We are no longer part of the thread
	   group */
	   
	current->self_exec_id++;
			
	flush_signal_handlers(current);//原来指向用户空间子程序的,现在设为SIG_DEL,表示采取预设的响应方式
	flush_old_files(current->files);//对原有已打开文件的处理

	return 0;

mmap_failed:
flush_failed:
	spin_lock_irq(current->sigmask_lock);
	if (current->sig != oldsig)
		kfree(current->sig);
	current->sig = oldsig;
	spin_unlock_irq(current->sigmask_lock);
	return retval;
}

    make_private_signals,代码如下:

static inline int make_private_signals(void)
{
	struct signal_struct * newsig;

	if (atomic_read(current->sig->count) <= 1)//如果只是把父进程的信号处理表指针复制过来,而通过这指针来共享父进程的信号处理表,那么current->sig->count大于1
		return 0;
	newsig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);//自立门户,和copy_sighand()基本相同
	if (newsig == NULL)
		return -ENOMEM;
	spin_lock_init(&newsig->siglock);
	atomic_set(&newsig->count, 1);
	memcpy(newsig->action, current->sig->action, sizeof(newsig->action));
	spin_lock_irq(current->sigmask_lock);
	current->sig = newsig;
	spin_unlock_irq(current->sigmask_lock);
	return 0;
}

    读者也许会问:既然最终还是要把它复制过来,何不在当初一步就把它复制好了?这就是所谓"lazy computation"的概念:一件事情只有在非做不可时才做。虽然新创建的进程一般都会执行execve(),”走自己的路“,但这是没有保证的。如果创建的是线程那就不一定会执行execve(),如果一律在创建时就复制就可能造成浪费而且不符合要求。再说检查一下是否还在与父进程共享信号处理表(通过检查共享计数)所花费的代价是很小的。


    回到flush_old_exec,继续执行exec_mmap,代码如下:

static int exec_mmap(void)
{
	struct mm_struct * mm, * old_mm;

	old_mm = current->mm;
	if (old_mm && atomic_read(&old_mm->mm_users) == 1) {//如果共享计数为1时,表明对此空间的使用是独占的,也就是说这是从父进程复制过来的
		flush_cache_mm(old_mm);
		mm_release();
		exit_mmap(old_mm);//释放mm_struct数据结构以下的所有vm_area_struct数据结构(但是不包括mm_struct结构本身)
		flush_tlb_mm(old_mm);
		return 0;
	}

	mm = mm_alloc();//如果只是将指向mm_struct数据结构的指针复制给子进程,让子进程通过这个指针来共享父进程的用户空间,那就可以跳过释放用户空间这一步,直接就为子进程分配新的用户空间
	if (mm) {
		struct mm_struct *active_mm = current->active_mm;

		if (init_new_context(current, mm)) {//空语句
			mmdrop(mm);
			return -ENOMEM;
		}

		/* Add it to the list of mm‘s */
		spin_lock(&mmlist_lock);
		list_add(&mm->mmlist, &init_mm.mmlist);
		spin_unlock(&mmlist_lock);

		task_lock(current);
		current->mm = mm;//初始化
		current->active_mm = mm;
		task_unlock(current);
		activate_mm(active_mm, mm);
		mm_release();
		if (old_mm) {
			if (active_mm != old_mm) BUG();
			mmput(old_mm);//减少mm->mm_users计数,因为已经不共享了
			return 0;
		}
		mmdrop(active_mm);
		return 0;
	}
	return -ENOMEM;
}

void mmput(struct mm_struct *mm)
{
	if (atomic_dec_and_lock(&mm->mm_users, &mmlist_lock)) {
		list_del(&mm->mmlist);
		spin_unlock(&mmlist_lock);
		exit_mmap(mm);
		mmdrop(mm);
	}
}

    

    exit_mmap,释放mm_struct数据结构以下的所有vm_area_struct数据结构(但是不包括mm_struct结构本身),代码如下:

void exit_mmap(struct mm_struct * mm)
{
	struct vm_area_struct * mpnt;

	release_segments(mm);
	spin_lock(&mm->page_table_lock);
	mpnt = mm->mmap;
	mm->mmap = mm->mmap_avl = mm->mmap_cache = NULL;//全部重置
	spin_unlock(&mm->page_table_lock);
	mm->rss = 0;
	mm->total_vm = 0;
	mm->locked_vm = 0;
	while (mpnt) {
		struct vm_area_struct * next = mpnt->vm_next;
		unsigned long start = mpnt->vm_start;
		unsigned long end = mpnt->vm_end;
		unsigned long size = end - start;

		if (mpnt->vm_ops) {
			if (mpnt->vm_ops->close)
				mpnt->vm_ops->close(mpnt);
		}
		mm->map_count--;
		remove_shared_vm_struct(mpnt);
		flush_cache_range(mm, start, end);
		zap_page_range(mm, start, size);//清空对应的页目录表项和页表项
		if (mpnt->vm_file)
			fput(mpnt->vm_file);
		kmem_cache_free(vm_area_cachep, mpnt);//释放vm_struct数据结构
		mpnt = next;
	}

	/* This is just debugging */
	if (mm->map_count)
		printk("exit_mmap: map count is %d\n", mm->map_count);

	clear_page_tables(mm, FIRST_USER_PGD_NR, USER_PTRS_PER_PGD);
}

    回到flush_old_exec,会继续执行release_old_signals,代码如下:

static inline void release_old_signals(struct signal_struct * oldsig)
{
	if (current->sig == oldsig)//如果相等,说明起初是通过复制的方式共享
		return;
	if (atomic_dec_and_test(&oldsig->count))//如果不等,说明是通过指针的方式共享
		kmem_cache_free(sigact_cachep, oldsig);
}


    flush_signal_handlers,原来指向用户空间子程序的,现在设为SIG_DEL,表示采取预设的响应方式,代码如下:

void
flush_signal_handlers(struct task_struct *t)
{
	int i;
	struct k_sigaction *ka = &t->sig->action[0];
	for (i = _NSIG ; i != 0 ; i--) {
		if (ka->sa.sa_handler != SIG_IGN)
			ka->sa.sa_handler = SIG_DFL;
		ka->sa.sa_flags = 0;
		sigemptyset(&ka->sa.sa_mask);
		ka++;
	}
}
    当把信号处理表从父进程复制过来时,其中每个表项的值有三种可能:一种可能是SIN_IGN,表示不理睬;第二种是SIG_DFL,表示采取预设的响应方式;第三种就是指向一个用户空间的子程序。可是,现在整个用户空间都已经放弃了,怎么还能让信号处理表的表项指向用户空间的子程序呢?所以还得检查一遍,将指向服务程序的表项改成SIG_DFL。


    flush_old_files,代码如下:

static inline void flush_old_files(struct files_struct * files)
{
	long j = -1;

	write_lock(&files->file_lock);
	for (;;) {
		unsigned long set, i;

		j++;
		i = j * __NFDBITS;
		if (i >= files->max_fds || i >= files->max_fdset)
			break;
		set = files->close_on_exec->fds_bits[j];
		if (!set)
			continue;
		files->close_on_exec->fds_bits[j] = 0;
		write_unlock(&files->file_lock);
		for ( ; set ; i++,set >>= 1) {
			if (set & 1) {
				sys_close(i);
			}
		}
		write_lock(&files->file_lock);

	}
	write_unlock(&files->file_lock);
}

    根据close_on_exec位图,里面存储着表示哪些文件在执行一个新目标程序时应予关闭的信息。根据这个位图的指示将这些文件关闭,并且将此位图清成全0。


    至此,flush_old_exec已经解释完了,返回到load_aout_binary继续执行,为可执行文件的代码段和数据段分配空间并建立起页面映射,然后执行set_brk,为可执行文件的bss段分配空间并建立起页面映射,代码如下:

static void set_brk(unsigned long start, unsigned long end)
{
	start = PAGE_ALIGN(start);
	end = PAGE_ALIGN(end);
	if (end <= start)
		return;
	do_brk(start, end - start);
}


    继续执行setup_arg_pages,用户空间的堆栈区顶部为进程建立起一个虚拟区间,并将执行参数以及环境变量所占的物理页面与此虚拟区间建立起映射,代码如下:

int setup_arg_pages(struct linux_binprm *bprm)
{
	unsigned long stack_base;
	struct vm_area_struct *mpnt;
	int i;


	stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//STACK_TOP为3GB


	bprm->p += stack_base;//bprm->p原来是MAX_ARG_PAGES*PAGE_SIZE-参数的数量,现在加上STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE,最后是STACK_TOP - 参数的数量
	if (bprm->loader)
		bprm->loader += stack_base;
	bprm->exec += stack_base;


	mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
	if (!mpnt) 
		return -ENOMEM; 
	
	down(¤t->mm->mmap_sem);
	{
		mpnt->vm_mm = current->mm;
		mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//堆栈段开始地址
		mpnt->vm_end = STACK_TOP;//堆栈段结束地址
		mpnt->vm_page_prot = PAGE_COPY;
		mpnt->vm_flags = VM_STACK_FLAGS;
		mpnt->vm_ops = NULL;
		mpnt->vm_pgoff = 0;
		mpnt->vm_file = NULL;
		mpnt->vm_private_data = (void *) 0;
		insert_vm_struct(current->mm, mpnt);
		current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
	} 


	for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
		struct page *page = bprm->page[i];//从page[0]开始
		if (page) {
			bprm->page[i] = NULL;
			current->mm->rss++;
			put_dirty_page(current,page,stack_base);//建立从堆栈虚拟空间到页面之前的映射
		}
		stack_base += PAGE_SIZE;//加一个页面的长度,4096个字节
	}
	up(¤t->mm->mmap_sem);
	
	return 0;
}

void put_dirty_page(struct task_struct * tsk, struct page *page, unsigned long address)
{
	pgd_t * pgd;
	pmd_t * pmd;
	pte_t * pte;

	if (page_count(page) != 1)
		printk("mem_map disagrees with %p at %08lx\n", page, address);
	pgd = pgd_offset(tsk->mm, address);
	pmd = pmd_alloc(pgd, address);
	if (!pmd) {
		__free_page(page);
		force_sig(SIGKILL, tsk);
		return;
	}
	pte = pte_alloc(pmd, address);
	if (!pte) {
		__free_page(page);
		force_sig(SIGKILL, tsk);
		return;
	}
	if (!pte_none(*pte)) {
		pte_ERROR(*pte);
		__free_page(page);
		return;
	}
	flush_dcache_page(page);
	flush_page_to_ram(page);
	set_pte(pte, pte_mkdirty(pte_mkwrite(mk_pte(page, PAGE_COPY))));
/* no need for flush_tlb */
}


    create_aout_tables,把一些变量放入用户堆栈中,代码如下:

static unsigned long * create_aout_tables(char * p, struct linux_binprm * bprm)
{
	char **argv, **envp;
	unsigned long * sp;
	int argc = bprm->argc;
	int envc = bprm->envc;

	sp = (unsigned long *) ((-(unsigned long)sizeof(char *)) & (unsigned long) p);
#ifdef __sparc__
	/* This imposes the proper stack alignment for a new process. */
	sp = (unsigned long *) (((unsigned long) sp) & ~7);
	if ((envc+argc+3)&1) --sp;
#endif
#ifdef __alpha__
/* whee.. test-programs are so much fun. */
	put_user(0, --sp);
	put_user(0, --sp);
	if (bprm->loader) {
		put_user(0, --sp);
		put_user(0x3eb, --sp);
		put_user(bprm->loader, --sp);
		put_user(0x3ea, --sp);
	}
	put_user(bprm->exec, --sp);
	put_user(0x3e9, --sp);
#endif
	sp -= envc+1;
	envp = (char **) sp;
	sp -= argc+1;
	argv = (char **) sp;
#if defined(__i386__) || defined(__mc68000__) || defined(__arm__)
	put_user((unsigned long) envp,--sp);
	put_user((unsigned long) argv,--sp);
#endif
	put_user(argc,--sp);
	current->mm->arg_start = (unsigned long) p;
	while (argc-->0) {
		char c;
		put_user(p,argv++);
		do {
			get_user(c,p++);
		} while (c);
	}
	put_user(NULL,argv);
	current->mm->arg_end = current->mm->env_start = (unsigned long) p;//env_start和arg_end
	while (envc-->0) {
		char c;
		put_user(p,envp++);
		do {
			get_user(c,p++);
		} while (c);
	}
	put_user(NULL,envp);
	current->mm->env_end = (unsigned long) p;//env_end
	return sp;//返回的是STACK_TOP减去图中参数所占空间
}
    形成如下图:

技术分享

    最后执行一个关键性的方法,start_thread,代码如下:

#define start_thread(regs, new_eip, new_esp) do {			__asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));		set_fs(USER_DS);						regs->xds = __USER_DS;						regs->xes = __USER_DS;						regs->xss = __USER_DS;						regs->xcs = __USER_CS;						regs->eip = new_eip;						regs->esp = new_esp;					} while (0)
    我们已经对这里的regs指针已经很熟悉了,它指向保留在当前进程系统空间堆栈中的各个寄存器副本。当进程从系统调用返回时,这些数值就会被"恢复"到CPU的各个寄存器中。所以那时候的堆栈指针将是current->mm->start_stack;而返回地址,也将是ex.a_entry。显然,这正是我们所需要的。

Linux内核源代码情景分析-execve()

标签:

原文地址:http://blog.csdn.net/jltxgcy/article/details/44489225

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!