深入Linux内核架构-进程管理和调度(七)

一、启动新程序

通过用新代码替换现存程序,即可启动新程序。Linux提供的execve系统调用可用于该目的。

1、execve的实现

该系统调用的入口点是体系结构相关的sys_execve函数。该函数很快将其工作委托给系统无关的do_execve例程。

fs/exec.c

int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp,  struct pt_regs * regs)

这里不仅用参数传递了寄存器集合和可执行文件的名称( filename),而且还传递了指向程序的参数和环境的指针。因为argv和envp都是指针数组,而且指向两个数组自身的指针以及数组中的所有指针都位于虚拟地址空间的用户空间部分。在内核访问用户空间内存时需要多加小心,而__user注释则允许自动化工具来检测是否所有相关事宜都处理得当。

图2-11给出了do_execve的代码流程图。

首先打开要执行的文件。按第8章的说法,内核找到相关的inode并生成一个文件描述符,用于寻址该文件。

bprm_mm_init接下来处理若干管理性任务:mm_alloc生成一个新的mm_struct实例来管理进程地址空间。init_new_context是一个特定于体系结构的函数,用于初始化该实例,而__bprm_mm_init则建立初始的栈。

新进程的各个参数(例如, euid、 egid、参数列表、环境、文件名,等等)随后会分别传递给其他函数,此时为简明起见,则合并成一个类型为linux_binprm的结构。

prepare_binprm用于提供一些父进程相关的值(特别是有效UID和GID)。剩余的数据,即参数列表,接下来直接复制到该结构中。要注意prepare_binprm也维护了对SUID(设置用户id)和SGID(设置组id)位的处理:

fs/exec.c

在确认文件来源卷在装载时没有置位MNT_NOSUID之后,内核会检测SUID(设置用户id)或SGID(设置组id)位是否置位。第
一种情况很容易处理:如果S_ISUID置位,那么有效UID与inode相同(否则,使用进程的有效UID)。SGID的情况类似,但内核还需要确认组执行位也已经置位。

Linux支持可执行文件的各种不同组织格式。标准格式是ELFExecutable and Linkable Format)。

尽管在不同的体系结构上可能使用许多二进制格式( ELF尽可能设计得与系统无关),这并不意味着特定二进制格式中的程序能够在多个体系结构上运行。不同处理器使用的汇编语言语句仍然非常不同,而二进制格式只表示如何在可执行文件和内存中组织程序的各个部分(数据、代码,等等)。

search_binary_handler用于在do_execve结束时查找一种适当的二进制格式,用于所要执行的特定文件。这种查找是可能的,因为各种格式可根据不同的特点来识别(通常是文件起始处的一个“魔数”)。二进制格式处理程序负责将新程序的数据加载到旧的地址空间中。

通常,二进制格式处理程序执行下列操作。

释放原进程使用的所有资源。

将应用程序映射到虚拟地址空间中。必须考虑下列段的处理(涉及的变量是task_struct的成员,由二进制格式处理程序设置为正确的值)。

1)text段包含程序的可执行代码。start_code和end_code指定该段在地址空间中驻留的区域。

2)预先初始化的数据(在编译时间指定了具体值的变量)位于start_data和end_data之间,映射自可执行文件的对应段。

堆( heap)用于动态内存分配,置于虚拟地址空间中。start_brk和brk指定了其边界。

栈的位置由start_stack定义。几乎所有的计算机上栈都是自动地向下增长。唯一的例外是当前的PA-Risc(惠普公司的RISC芯片)。对于栈的反向增长,体系结构相关部分的实现必须告知内核,可通过设置配置符号STACK_GROWSUP完成。

程序的参数和环境也映射到虚拟地址空间中,分别位于arg_start和arg_end之间,以及env_start和env_end之间。

3)设置进程的指令指针和其他特定于体系结构的寄存器,以便在调度器选择该进程时开始执行程序的main函数。

Linux支持的二进制格式

aout_format                  a.out是引入ELF之前Linux的标准格式。因为它太不灵活,所以现在很少使用。

som_format                  在PA-Risc计算机上使用,特定于HP-UX的格式。

irix_format                     ELF格式变体,提供了特定于Irix的特性。

elf_format                      这是一种与计算机和体系结构无关的格式,可用于32位和64位。它是Linux的标准格式。

elf_fdpic_format            ELF格式变体,提供了针对没有MMU(内存管理单元)系统的特别特性。

misc_format                  是一种伪格式,用于启动需要外部解释器的应用程序。与#!机制相比,解释器无须显式指定,而可以通过特定的文件标识符(后缀、文件头,等等)确定。例如,该格式用于执行Java字节代码或用Wine运行Windows程序。

script_format                 是一种伪格式,用于运行使用#!机制的脚本。检查文件的第一行,内核即知道使用何种解释器,启动适当的应用程序即可(例如,如果是#! /usr/bin/perl,则启动Perl)。

flat_format                     平坦格式用于没有内存管理单元( MMU)的嵌入式CPU上。为节省空间,可执行文件中的数据还可以压缩(如果内核可提供zlib支持)。

2、解释二进制格式

在Linux内核中,每种二进制格式都表示为下列数据结构的一个实例:

linux/binfmts.h

每种二进制格式必须提供下面3个函数。

(1) load_binary用于加载普通程序。

(2) load_shlib用于加载共享库,即动态库。

(3) core_dump用于在程序错误的情况下输出内存转储。该转储随后可使用调试器(例如, gdb)分析,以便解决问题。min_coredump是生成内存转储时,内存转储文件长度的下界(通常,这是一个内存页的长度)。

每种二进制格式首先必须使用register_binfmt向内核注册。该函数的目的是向一个链表增加一种新的二进制格式,该链表的表头是fs/exec.c中的全局变量formats。

二、退出进程

进程必须用exit系统调用终止。这使得内核有机会将该进程使用的资源释放回系统。该调用的入口点是sys_exit函数,需要一个错误码作为其参数,以便退出进程。其定义是体系结构无关的,见kernel/exit.c。对其实现没什么兴趣,因为它很快将工作委托给do_exit。

该函数的实现就是将各个引用计数器减1,如果引用计数器归0而没有进程再使用对应的结构,那么将相应的内存区域返还给内存管理模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值