date: 2014-10-24 12:09

1 用户空间的编程接口

这部分详情请参考APUE(第2版)第8章。

1.1 六种exec函数

有6种不同的exec函数可供使用,这些函数最终都是通过系统调用execve来实现的:

int execl(const char *pathname, const char *arg1, ... /* (char*)0 */ );

int execlp(const char *filename, const char *arg1, ... /* (char*)0 */ );

int execle(const char *pathname, const char *arg1, ...

/* (char*)0, char * const *envp */);

int execv(const char *pathname, char * const argv[]);

int execvp(const char *filename, char * const argv[]);

int execve(const char *pathname, char * const argv[], char * const envp[]);

它们间的关系如下图:

1.2 进程相关的ID

在《进程四要素》中我们简单看了下task_struct结构,其中有6个与进程相关的ID:

ID

意义

备注

uid/gid

实际用户ID/实际组ID

我实际上是谁

euid/egid

有效用户ID/有效组ID

我还具有哪些额外的“特权”

suid/sgid

保存的设置用户ID/保存的设置组ID

由exec函数保存

通常进程的有效ID就是用户的实际ID,但当进程执行一个程序文件时(通过execve系统调用),如果可执行文件设置了set-user-ID(设置用户ID)位或set-group-ID(设置组ID)位,那么执行该程序文件的进程,其有效用户ID将被设置为程序文件的所有者ID,其有效组ID将被被设置为程序文件所在组的ID,这样,进程就具有一些额外的“特权“了。同时execve系统调用还会将设置后的有效用户ID保存到“保存的设置用户ID”中(对“保存的设置组ID”也是同样的处理),以方便其他函数使用,比如setuid函数需会根据“保存的设置用户ID”来判断是否可以将进程的有效ID设置为某个指定的用户ID。

2 系统调用execve

这部分我们重点关注下如下问题:

子进程是如何摆脱父进程自立门户的?子进程如何摆脱对父进程用户空间的依赖?

为什么说execve“一去不复返”?即为什么execve无法返回到(父进程)用户空间调用execve的地方?那么该系统调用返回到用户空间时,又返回到了哪里?

有效用户ID及有效组ID的处理。

传递给execve系统调用的argv如何传递给可执行文件的入口main函数?

这里假定execve执行的程序文件为aout格式的,具体来说是aout格式中的“非可重入代码”,即可执行程序包含正文段(text)、数据段(data)和未初始化数据段(bss)。虽然aout格式已非主流,elf才是当前流行的可执行程序文件的格式,但elf格式比较复杂,涉及到动态加载(loader)与动态链接(linker),而aout格式相对简单,用来了解上述问题是比较合适的。这些问题的答案同样适用于elf格式(或其他格式)的可执行文件。

系统调用execve的内核入口为sys_execve,定义在中:

asmlinkage int sys_execve(struct pt_regs regs)

{

int error;

char * filename;

filename = getname((char *) regs.ebx);

error = PTR_ERR(filename);

if (IS_ERR(filename))

goto out;

error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, &regs);

if (error == 0)

current->ptrace &= ~PT_DTRACE;

putname(filename);

out:

return error;

}

regs.ebx保存着系统调用execve的第一个参数,即可执行文件的路径名。因为路径名存储在用户空间中,这里要通过getname拷贝到内核空间中。getname在拷贝文件名时,先申请了一个page作为缓冲,然后再从用户空间拷贝字符串。为什么要申请一个页面而不使用进程的系统空间堆栈?首先这是一个绝对路径名,可能比较长,其次进程的系统空间堆栈大约为7K,比较紧缺,不宜滥用。用完文件名后,在函数的末尾调用putname释放掉申请的那个页面。

sys_execve的核心是调用do_execve函数,传给do_execve的第一个参数是已经拷贝到内核空间的路径名filename,第二个和第三个参数仍然是系统调用execve的第二个参数argv和第三个参数envp,它们代表的传给可执行文件的参数和环境变量仍然保留在用户空间中。

2.1 do_execve主要流程

do_execve定义在中。它的主要流程(忽略掉异常情况的处理)如下:

2.2 linux_binprm结构

可执行文件(目标文件)作为一个文件之外,还有一些其他的专属信息,为了将运行一个可执行文件时所需的信息组织在一起,内核定义了linux_binprm结构,其定义如下:

struct linux_binprm{

char buf[BINPRM_BUF_SIZE];

struct page *page[MAX_ARG_PAGES];

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;

};

buf用来从可执行文件中读入前128个字节,据此可以判断处可执行文件的类型(比如aout、elf、java、或者脚本等)。

page是一个物理页面指针数组,这些物理页面用来存储execve系统调用中参数argv以及envp所指向的字符串表。数组的size为MAX_ARG_PAGES(32),但具体会分配多少个物理页面,取决于argv已经envp所指向的字符串表的大小。

p用来指向page数组所代表的存储空间的“游标”。

file即可执行文件对应的文件表项。

当可执行文件设置了set-user-ID或者set-group-ID,e_uid和e_gid分别用来存储可执行文件的所有者ID和所在组ID.

filename指向可执行文件的路径(该路径字符串已经拷贝到内核空间)。

2.3 linux_binfmt结构以及search_binary_handler

每一种可执行文件都有对应的“装载器”,用来处理可执行文件的加载甚至是链接,此即linux_binfmt结构。其定义如下:

struct linux_binfmt {

struct linux_binfmt * next;

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 min_coredump;/* minimal dump size */

};

其中关键的是几个函数指针,顾名思义,load_binary用来加载可执行文件;load_shlib用来加载共享库;而core_dump用来生成转储文件。

不同的“加载器”通过next指针构成一个链表,链表头即为formats。

每个加载器就像是内核为每种格式的可执行文件设置的代理人,每当执行一个可执行文件时,内核遍历formats中的每个代理人,查看该可执行文件是否归某个代理人处理,如果对上了号,代理人则“认领”该可执行文件,负责后续的加载、执行等事务。这就是search_binary_handler函数的主要工作工程。但具体情况比这复杂,需要考虑内核尚未为某种格式的可执行文件设置代理人的情形。

aout格式对应的inux_binfmt结构为aout_format,其定义如下:

static struct linux_binfmt aout_format = {

NULL,

THIS_MODULE,

load_aout_binary,

load_aout_library,

aout_core_dump,

PAGE_SIZE

};

可见aout类可执行文件的加载函数为load_aout_binary,这是流程图中的重点。

2.4 目标文件在内存中的布局如下图所示:

2.5 start_thread

在可执行文件加载完成,并且传递给main函数的argc和argv参数处理完毕后,load_aout_binary调用start_thread来设置子进程返回用户空间后的入口(即main函数)以及用户空间堆栈的栈顶指针。

start_thread(regs, ex.a_entry, current->mm->start_stack);

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)

可见,这里将aout文件的入口ex. a_entry写进eip,而将准备好argc以及argv之后用户空间堆栈的栈顶current->mm->start_stack写进esp,这样当从系统调用返回到子进程的用户空间中时,将从aout文件的入口main函数开始执行,并且通过esp可以获取传递给main函数的argc和argv参数。

execve系统调用_进程调度之 4:系统调用execve相关推荐

  1. 操作系统系统调用_操作系统中的系统调用

    操作系统系统调用 系统调用简介 (Introduction to System calls) The interface between the operating system and the us ...

  2. execve系统调用_《操作系统》知识结构

    作者:陈凌灏 单位:西安电子科技大学 注:转载请注明出处~~ 写在前面:<操作系统>这门课几乎可以说是计算机专业最重要的课程了.本文主要是在学习完这门课后,参照TANENBAUM的< ...

  3. execve系统调用_张凯捷—系统调用分析(3) (基于最新Linux5.0版本系统调用日志收集系统)...

    在上一篇文章<系统调用分析(2)>中介绍和分析了32位和64位的快速系统调用指令--sysenter/sysexit和syscall/sysret,以及内核对快速系统调用部分的相关代码,并 ...

  4. php跟踪系统调用,使用strace命令跟踪系统调用

    一.是什么strace? strace常用来跟踪进程执行时的系统调用和所接收的信号. 在Linux世界,进程不能直接访问硬件设备,当进程需要访问硬件设备(比如读取磁盘文件,接收网络数据等等)时,必须由 ...

  5. 操作系统【用户接口】命令解释程序的主要功能、系统调用与一般过程调用的不同之处、系统调用的参数传递方式、系统调用的处理步骤

    操作系统 第九章 接口--用户接口   系统安全 命令解释程序:命令解释程序的主要功能: 系统调用:①系统调用与一般过程调用的不同之处②系统调用的参数传递方式③系统调用的处理步骤 命令解释程序的主要功 ...

  6. execve系统调用_系统调用execve的入口sys_execve() | 学步园

    /* * sys_execve() executes a new program. */ long sys_execve(const char __user *name, //需要执行的文件的绝对路径 ...

  7. execve系统调用_Linux下的sys_execve系统调用

    传统Int 0x80系统调用 系统调用号:EAX 参数:EBX.ECX.EDX.ESI.EDI.EBP 返回值:EAX 具体功能号在 unistd_32.h 文件中 64位系统调用syscall 系统 ...

  8. Golang标准库-syscall(什么是系统调用/Go 语言中的系统调用)

    文章目录 一.什么是系统调用 二.Golang标准库-syscall 1. syscall无处不在 2. syscall demo举例: go版本的strace Strace go版本的strace ...

  9. 批处理结束某个进程_进程调度

    当计算机系统是多道程序设计系统时,常常会出现多个进程或线程竞争CPU的情况.如果有大于处理器数的进程(线程)处于就绪态时,就必须要选择下一个要执行的进程(线程).在操作系统,用于选择接下来要执行的进程 ...

最新文章

  1. 求高维方阵的绝对值最大特征值的方法:幂法(幂迭代)。
  2. ​IBM人工智能芯片的新进展
  3. commons-fileupload、smartUpload和commons-net-ftp
  4. ibatis和hibernate
  5. leetcode306 2022.1.10
  6. 我的大学--单片机高手郭天祥的学习心得体会
  7. android多屏互动方案,史上最实用的多屏互动教程之PC投屏安卓篇
  8. CDA数据分析师-LEVEL I考试-分享
  9. 人才管理是什么意思_什么是iTM?人才管理iTM是什么意思?
  10. Redis配置详解(转)
  11. _ETHREAD断链 —— 实现线程隐藏
  12. 工程师小哥魔术揭秘“三仙归洞”,把我都看蒙了!
  13. Ubuntu16 wine安装迅雷
  14. 立创商城中的元件应用到AD中的一些经验
  15. Python---第8天---字符串
  16. 怎样使用JS代码代码跳转的方法
  17. SDN(一) 用OpenWRT将路由器刷成openflow交换机
  18. [IAR] 编译报错:Variable expansion failed for Pre-Build command line
  19. 中南大学软件工程修习课程
  20. 成都自传-MoccA and 555

热门文章

  1. LeetCode 718 最长重复子数组
  2. Verilog0.2:跑通第一个Vivado工程
  3. html打印为pdf表格显示不全,打印表格时内容显示不完整怎么办?四种方法解决WPS不完整问题...
  4. 最新海洋CMS(海洋视频内容管理系统)V12.6版
  5. Android10无法访问根目录文件解决
  6. 会员管理-小程序-免费使用体验
  7. K3运行时错误91解决方法
  8. PHPMailer远程命令执行漏洞复现
  9. ZbxTable 2.1 重磅发布!周四直播分享
  10. matlab定积分怎么输入,Matlab计算定积分的操作内容讲解