Linux进程管理之执行新的程序

    内核源码: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环境高级编程》):

 Linux进程管理之执行新的程序_第1张图片

    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);

 

你可能感兴趣的:(linux,struct,user,File,recursion,credentials)