You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

724 lines
27 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* linux/fs/exec.c
*
* (C) 1991 Linus Torvalds
*/
/*
* #!-checking implemented by tytso.
*/
/*
* Demand-loading implemented 01.12.91 - no need to read anything but
* the header into memory. The inode of the executable is put into
* "current->executable", and page faults do the actual loading. Clean.
*
* Once more I can proudly say that linux stood up to being changed: it
* was less than 2 hours work to get demand-loading completely implemented.
*/
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <a.out.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/segment.h>
extern int sys_exit(int exit_code);
extern int sys_close(int fd);
/*
* MAX_ARG_PAGES defines the number of pages allocated for arguments
* and envelope for the new program. 32 should suffice, this gives
* a maximum env+arg of 128kB !
*/
#define MAX_ARG_PAGES 32
int sys_uselib()
{
return -ENOSYS;
}
/*
* create_tables() parses the env- and arg-strings in new user
* memory and creates the pointer tables from them, and puts their
* addresses on the "stack", returning the new stack pointer value.
*/
static unsigned long * create_tables(char * p,int argc,int envc)
{
unsigned long *argv,*envp;
unsigned long * sp;
sp = (unsigned long *) (0xfffffffc & (unsigned long) p);
sp -= envc+1;
envp = sp;
sp -= argc+1;
argv = sp;
put_fs_long((unsigned long)envp,--sp);
put_fs_long((unsigned long)argv,--sp);
put_fs_long((unsigned long)argc,--sp);
while (argc-->0) {
put_fs_long((unsigned long) p,argv++);
while (get_fs_byte(p++)) /* nothing */ ;
}
put_fs_long(0,argv);
while (envc-->0) {
put_fs_long((unsigned long) p,envp++);
while (get_fs_byte(p++)) /* nothing */ ;
}
put_fs_long(0,envp);
return sp;
}
/*
* count() counts the number of arguments/envelopes
*/
static int count(char ** argv)
{
int i=0;
char ** tmp;
if (tmp = argv)
while (get_fs_long((unsigned long *) (tmp++)))
i++;
return i;
}
/*
* 'copy_string()' copies argument/envelope strings from user
* memory to free pages in kernel mem. These are in a format ready
* to be put directly into the top of new user memory.
*
* Modified by TYT, 11/24/91 to add the from_kmem argument, which specifies
* whether the string and the string array are from user or kernel segments:
*
* from_kmem argv * argv **
* 0 user space user space
* 1 kernel space user space
* 2 kernel space kernel space
*
* We do this by playing games with the fs segment register. Since it
* it is expensive to load a segment register, we try to avoid calling
* set_fs() unless we absolutely have to.
*/
static unsigned long copy_strings(int argc,char ** argv,unsigned long *page,
unsigned long p, int from_kmem)
{
char *tmp, *pag;
int len, offset = 0;
unsigned long old_fs, new_fs;
if (!p)
return 0; /* bullet-proofing */
new_fs = get_ds();
old_fs = get_fs();
if (from_kmem==2)
set_fs(new_fs);
while (argc-- > 0) {
if (from_kmem == 1)
set_fs(new_fs);
if (!(tmp = (char *)get_fs_long(((unsigned long *)argv)+argc)))
panic("argc is wrong");
if (from_kmem == 1)
set_fs(old_fs);
len=0; /* remember zero-padding */
do {
len++;
} while (get_fs_byte(tmp++));
if (p-len < 0) { /* this shouldn't happen - 128kB */
set_fs(old_fs);
return 0;
}
while (len) {
--p; --tmp; --len;
if (--offset < 0) {
offset = p % PAGE_SIZE;
if (from_kmem==2)
set_fs(old_fs);
if (!(pag = (char *) page[p/PAGE_SIZE]) &&
!(pag = (char *) (page[p/PAGE_SIZE] =
(unsigned long *) get_free_page())))
return 0;
if (from_kmem==2)
set_fs(new_fs);
}
*(pag + offset) = get_fs_byte(tmp);
}
}
if (from_kmem==2)
set_fs(old_fs);
return p;
}
static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
{
unsigned long code_limit,data_limit,code_base,data_base;
int i;
code_limit = text_size+PAGE_SIZE -1;
code_limit &= 0xFFFFF000;
data_limit = 0x4000000;
code_base = get_base(current->ldt[1]);
data_base = code_base;
set_base(current->ldt[1],code_base);
set_limit(current->ldt[1],code_limit);
set_base(current->ldt[2],data_base);
set_limit(current->ldt[2],data_limit);
/* make sure fs points to the NEW data segment */
__asm__("pushl $0x17\n\tpop %%fs"::);
data_base += data_limit;
for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
data_base -= PAGE_SIZE;
if (page[i])
put_page(page[i],data_base);
}
return data_limit;
}
/*
* 'do_execve()' executes a new program.
*/
int do_execve(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
struct m_inode * inode;
struct buffer_head * bh;
struct exec ex;
unsigned long page[MAX_ARG_PAGES];
int i,argc,envc;
int e_uid, e_gid;
int retval;
int sh_bang = 0;
unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4;
if ((0xffff & eip[1]) != 0x000f)
panic("execve called from supervisor mode");
for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */
page[i]=0;
if (!(inode=namei(filename))) /* get executables inode */
return -ENOENT;
argc = count(argv);
envc = count(envp);
restart_interp:
if (!S_ISREG(inode->i_mode)) { /* must be regular file */
retval = -EACCES;
goto exec_error2;
}
i = inode->i_mode;
e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
if (current->euid == inode->i_uid)
i >>= 6;
else if (current->egid == inode->i_gid)
i >>= 3;
if (!(i & 1) &&
!((inode->i_mode & 0111) && suser())) {
retval = -ENOEXEC;
goto exec_error2;
}
if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
retval = -EACCES;
goto exec_error2;
}
ex = *((struct exec *) bh->b_data); /* read exec-header */
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
/*
* This section does the #! interpretation.
* Sorta complicated, but hopefully it will work. -TYT
*/
char buf[1023], *cp, *interp, *i_name, *i_arg;
unsigned long old_fs;
strncpy(buf, bh->b_data+2, 1022);
brelse(bh);
iput(inode);
buf[1022] = '\0';
if (cp = strchr(buf, '\n')) {
*cp = '\0';
for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
}
if (!cp || *cp == '\0') {
retval = -ENOEXEC; /* No interpreter name found */
goto exec_error1;
}
interp = i_name = cp;
i_arg = 0;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
if (*cp) {
*cp++ = '\0';
i_arg = cp;
}
/*
* OK, we've parsed out the interpreter name and
* (optional) argument.
*/
if (sh_bang++ == 0) {
p = copy_strings(envc, envp, page, p, 0);
p = copy_strings(--argc, argv+1, page, p, 0);
}
/*
* Splice in (1) the interpreter's name for argv[0]
* (2) (optional) argument to interpreter
* (3) filename of shell script
*
* This is done in reverse order, because of how the
* user environment and arguments are stored.
*/
p = copy_strings(1, &filename, page, p, 1);
argc++;
if (i_arg) {
p = copy_strings(1, &i_arg, page, p, 2);
argc++;
}
p = copy_strings(1, &i_name, page, p, 2);
argc++;
if (!p) {
retval = -ENOMEM;
goto exec_error1;
}
/*
* OK, now restart the process with the interpreter's inode.
*/
old_fs = get_fs();
set_fs(get_ds());
if (!(inode=namei(interp))) { /* get executables inode */
set_fs(old_fs);
retval = -ENOENT;
goto exec_error1;
}
set_fs(old_fs);
goto restart_interp;
}
brelse(bh);
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
retval = -ENOEXEC;
goto exec_error2;
}
if (N_TXTOFF(ex) != BLOCK_SIZE) {
printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
retval = -ENOEXEC;
goto exec_error2;
}
if (!sh_bang) {
p = copy_strings(envc,envp,page,p,0);
p = copy_strings(argc,argv,page,p,0);
if (!p) {
retval = -ENOMEM;
goto exec_error2;
}
}
/* OK, This is the point of no return */
if (current->executable)
iput(current->executable);
current->executable = inode;
for (i=0 ; i<32 ; i++)
current->sigaction[i].sa_handler = NULL;
for (i=0 ; i<NR_OPEN ; i++)
if ((current->close_on_exec>>i)&1)
sys_close(i);
current->close_on_exec = 0;
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
if (last_task_used_math == current)
last_task_used_math = NULL;
current->used_math = 0;
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
p = (unsigned long) create_tables((char *)p,argc,envc);
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
current->start_stack = p & 0xfffff000;
current->euid = e_uid;
current->egid = e_gid;
i = ex.a_text+ex.a_data;
while (i&0xfff)
put_fs_byte(0,(char *) (i++));
eip[0] = ex.a_entry; /* eip, magic happens :-) */
eip[3] = p; /* stack pointer */
return 0;
exec_error2:
iput(inode);
exec_error1:
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(page[i]);
return(retval);
}
/*
int execve2(const char *path, char * argv[], char * envp[]);
功能:以⽴即加载⽅式执⾏⼀个指定的程序。此系统调⽤开始后,该进程不应再发⽣代码段和数据段中
的缺⻚故障。
输⼊:
path: 待执⾏程序路径名称,
argv: 程序的参数,
envp: 环境变量的数组指针
返回值:成功不返回,失败返回-1
* coded by dj
*/
int do_execve2(unsigned long * eip,long tmp,char * filename,
char ** argv, char ** envp)
{
struct m_inode * inode;
struct buffer_head * bh;
struct exec ex;
unsigned long page[MAX_ARG_PAGES]; // 参数和环境串空间页面指针数组。
int i,argc,envc;
int e_uid, e_gid; // 有效用户 ID 和有效组 ID。
int retval;
int sh_bang = 0; // 控制是否需要执行脚本程序。
unsigned long p=PAGE_SIZE*MAX_ARG_PAGES-4; // p 指向参数和环境空间的最后部
unsigned long end_ad,address;
/*
在正式设置执行文件的运行环境之前,让我们先做些准备工作。
内核准备了 128KB32 个页面)空间来存放化执行文件的命令行参数和环境字符串。
上行把 p 初始设置成位于 128KB 空间的最后1 个长字处。在初始参数和环境空间的操作过程中,
p 将用来指明在 128KB 空间中的当前位置。
另外,参数 eip[1]是调用本次系统调用的原用户程序代码段寄存器 CS 值,其中的段选择符当然
必须是当前任务的代码段选择符0x000f。 若不是该值,那么 CS 只能会是内核代码段的选择
符 0x0008。 但这是绝对不允许的,因为内核代码是常驻内存而不能被替换掉的。
因此下面根据eip[1]的值确认是否符合正常情况。然后再初始化 128KB 的参数和环境串空间,把所有字节清零,
并取出执行文件的 i 节点。再根据函数参数分别计算出命令行参数和环境字符串的个数 argc 和
envc。另外执行文件必须是常规文件。
*/
if ((0xffff & eip[1]) != 0x000f)
panic("execve called from supervisor mode");
for (i=0 ; i<MAX_ARG_PAGES ; i++) /* clear page-table */
page[i]=0;
if (!(inode=namei(filename))) /* get executables inode */
return -ENOENT;
argc = count(argv);
envc = count(envp); //参数个数
restart_interp:
if (!S_ISREG(inode->i_mode)) { /* must be regular file */
retval = -EACCES;
goto exec_error2; //若不是常规文件则置出错码,跳转
}
/*
下面检查当前进程是否有权运行指定的执行文件。即根据执行文件 i 节点中的属性,
看看本进程是否有权执行它。在把执行文件 i 节点的属性字段值取到 i 中后,我们
首先查看属性中是否设置了“设置-用户-ID”set-user_id标志和“设置-组-ID”
set-group-id标志。这两个标志主要用于让一般用户能够执行特权用户 (如超级
用户 root的程序例如改变密码的程序passwd 等。
如果 set-user-id 标志置位,则后面执行进程的有效用户 IDeuid就设置成执行文件
的用户 ID否则设置成当前进程的 euid。
如果执行文件 set-group-id 被置位的话,则执行进程的有效组 IDegid就设置为执行
文件的组 ID否则设置成当前进程的 egid。这里暂时把这两个判断出来的值保存在变量
e_uid 和 e_gid 中。
*/
i = inode->i_mode;
e_uid = (i & S_ISUID) ? inode->i_uid : current->euid;
e_gid = (i & S_ISGID) ? inode->i_gid : current->egid;
/*
现在根据进程的 euid 和 egid 与执行文件的访问属性进行比较。如果执行文件属于
运行进程的用户,则把文件属性值 i 右移 6 位,此时其最低 3 位是文件宿主的访问
权限标志。否则的话如果执行文件与当前进程的用户属于同组,则使属性值最低 3 位
是执行文件组用户的访问权限标志。否则此时属性字最低 3 位就是其他用户访问该执
行文件的权限。
然后我们根据该最低 3 比特值来判断当前进程是否有权限运行这个执行文件。如果选出
的相应用户没有运行改文件的权力(位 0 是执行权限),并且其他用户也没有任何权限
或者当前进程用户不是超级用户,则表明当前进程没有权力运行这个执行文件。于是置不
可执行出错码并跳转到exec_error2 处去作退出处理。
*/
if (current->euid == inode->i_uid)
i >>= 6;
else if (current->egid == inode->i_gid)
i >>= 3;
if (!(i & 1) &&
!((inode->i_mode & 0111) && suser())) {
retval = -ENOEXEC;
goto exec_error2;
}
/*
若程序能执行到这里,说明当前进程有运行指定执行文件的权限。因此从这里开始我们
需要取出执行文件首部的数据,并根据其中的信息来分析设置运行环境,或者运行另一个
shell 程序来执行脚本程序。首先读取执行文件的第一块数据到高速缓冲块中,并复制
缓冲块数据到 ex 结构中。
如果执行文件开始的两个字节是字符'#!',则说明执行文件是一个脚本文本文件。若要
运行脚本文件,我们就需要执行脚本文件的解释程序(例如 shell 程序)。通常脚本文件
的第一行文本均为 “#/bin/bash”它指明了运行脚本文件需要的解释程序。 运行方法
是从脚本文件第一行(带字符'#!')中取出其中的解释程序名及后面的参数(若有的话),
然后将这些参数和脚本文件名放进执行文件(此时是解释程序)的命令行参数空间中。在这
之前我们当然需要先把函数指定的原有命令行参数和环境字符串放到 128KB 空间中,而这
里建立起来的命令行参数则放到它们前面位置处(因为是逆向放置)。最后让内核执行脚本
文件的解释程序。
下面就是在设置好解释程序的脚本文件名等参数后,取出解释程序的 i 节点并跳转执行解释
程序。由于我们需要跳转到执行过的代码去,因此在确认并处理了脚本文件之后需要设置一个
禁止再次执行下面的脚本处理代码标志 sh_bang。在后面的代码中该标志也用来表示我们已经
设置好执行文件的命令行参数,不要重复设置。
*/
if (!(bh = bread(inode->i_dev,inode->i_zone[0]))) {
retval = -EACCES;
goto exec_error2;
}
ex = *((struct exec *) bh->b_data); /* read exec-header */
if ((bh->b_data[0] == '#') && (bh->b_data[1] == '!') && (!sh_bang)) {
/*
* This section does the #! interpretation.
* Sorta complicated, but hopefully it will work. -TYT
*/
char buf[1023], *cp, *interp, *i_name, *i_arg;
unsigned long old_fs;
/*
从这里开始,我们从脚本文件中提取解释程序名及其参数,并把解释程序名、解释程序的
参数和脚本文件名组合放入环境参数块中。首先复制脚本文件头 1 行字符'#!'后面的字符
串到 buf中其中含有脚本解释程序名例如/bin/sh也可能还包含解释程序的几个参
数。然后对buf 中的内容进行处理。删除开始的空格、制表符。
*/
strncpy(buf, bh->b_data+2, 1022);
brelse(bh);
iput(inode);
buf[1022] = '\0';
if (cp = strchr(buf, '\n')) {
*cp = '\0';
for (cp = buf; (*cp == ' ') || (*cp == '\t'); cp++);
}
if (!cp || *cp == '\0') {
retval = -ENOEXEC; /* No interpreter name found */
goto exec_error1;
}
/*
此时我们得到了开头是脚本解释程序名的一行内容(字符串)。下面分析该行。首先取第一
个字符串,它应该是解释程序名,此时 i_name 指向该名称。若解释程序名后还有字符,则
它们应该是解释程序的参数串,于是令 i_arg 指向该串。
*/
interp = i_name = cp;
i_arg = 0;
for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) {
if (*cp == '/')
i_name = cp+1;
}
if (*cp) {
*cp++ = '\0';
i_arg = cp;
}
/*
* OK, we've parsed out the interpreter name and
* (optional) argument.
*/
/*
现在我们要把上面解析出来的解释程序名 i_name 及其参数 i_arg 和脚本文件名作为解释程序
的参数放进环境和参数块中。不过首先我们需要把函数提供的原来一些参数和环境字符串先放进去,
然后再放这里解析出来的。
这里我们把 sh_bang 标志置上,然后把函数参数提供的原有参数和环境字符串放入到空间中。环
境字符串个数是 envc 个,参数个数是 argc-1 个。少复制的一个原有参数是原执行文件名,即这
里的脚本文件名,将在下面进行处理。
*/
if (sh_bang++ == 0) {
p = copy_strings(envc, envp, page, p, 0);
p = copy_strings(--argc, argv+1, page, p, 0);
}
/*
* Splice in (1) the interpreter's name for argv[0]
* (2) (optional) argument to interpreter
* (3) filename of shell script
*
* This is done in reverse order, because of how the
* user environment and arguments are stored.
*/
/*
由于本函数参数提供的脚本文件名filename 在用户空间,但这里赋予 copy_strings()
的脚本文件名的指针在内核空间,因此这个复制字符串函数的最后一个参数(字符串来源标志)
需要被设置成 1。若字符串在内核空间则 copy_strings()的最后一个参数要设置成 2
*/
p = copy_strings(1, &filename, page, p, 1);
argc++;
if (i_arg) {
p = copy_strings(1, &i_arg, page, p, 2);
argc++;
}
p = copy_strings(1, &i_name, page, p, 2);
argc++;
if (!p) {
retval = -ENOMEM;
goto exec_error1;
}
/*
* OK, now restart the process with the interpreter's inode.
*/
// OK现在使用解释程序的 i 节点重启进程。
/*
最后我们取得解释程序的 i 节点指针,然后跳转去执行解释程序。为了获得解释程序
的i 节点,我们需要使用 namei()函数,但是该函数所使用的参数(文件名)是从用户数据空间
得到的,即从段寄存器 FS 所指空间中取得。 因此在调用 namei()函数之前我们需要先临时让
FS 指向内核数据空间,以让函数能从内核空间得到解释程序名,并在 namei()返回后恢复 FS 的
默认设置。之后再跳转到 restart_interp处重新处理新的执行文件 -- 脚本文件的解释程序。
*/
old_fs = get_fs();
set_fs(get_ds());
if (!(inode=namei(interp))) { /* get executables inode */
set_fs(old_fs);
retval = -ENOENT;
goto exec_error1;
}
set_fs(old_fs);
goto restart_interp;
}
/*
此时缓冲块中的执行文件头结构数据已经复制到了 ex 中。于是先释放该缓冲块,并开始
对 ex中的执行头信息进行判断处理。对于这个内核来说它仅支持 ZMAGIC 执行文件格
式,并且执行文件代码都从逻辑地址 0 开始执行,因此不支持含有代码或数据重定位信息
的执行文件。当然,如果执行文件实在太大或者执行文件残缺不全,那么我们也不能运行它。
因此对于下列情况将不执行程序执行文件不是可执行文件ZMAGIC、或者代码和数据重
定位部分长度不等于 0、或者(代码段 + 数据段 + 堆)长度超过 50MB、 或者执行文件长度
小于 (代码段 + 数据段 + 符号表长度 + 执行头部分) 长度的总和。
*/
brelse(bh);
if (N_MAGIC(ex) != ZMAGIC || ex.a_trsize || ex.a_drsize ||
ex.a_text+ex.a_data+ex.a_bss>0x3000000 ||
inode->i_size < ex.a_text+ex.a_data+ex.a_syms+N_TXTOFF(ex)) {
retval = -ENOEXEC;
goto exec_error2;
}
/*
另外,如果执行文件中代码开始处没有位于 1 个页面1024 字节)边界处,则也不能执行。
因为需求页Demand paging技术要求加载执行文件内容时以页面为单位因此要求执行
文件映像中代码和数据都从页面边界处开始。
*/
if (N_TXTOFF(ex) != BLOCK_SIZE) {
printk("%s: N_TXTOFF != BLOCK_SIZE. See a.out.h.", filename);
retval = -ENOEXEC;
goto exec_error2;
}
/*
如果 sh_bang 标志没有设置,则复制指定个数的命令行参数和环境字符串到参数和环境空间
中。若 sh_bang 标志已经设置,则表明是将运行脚本解释程序,此时环境变量页面已经复制,
无须再复制。同样,若 sh_bang 没有置位而需要复制的话,那么此时指针 p 随着复制信息增
加而逐渐向小地址方向移动,因此这两个复制串函数执行完后,环境参数串信息块位于程序
参数串信息块的上方,并且 p 指向程序的第 1 个参数串。事实上p 是 128KB 参数和环境空
间中的偏移值。因此如果 p=0则表示环境变量与参数空间页面已经被占满容纳不下了。
*/
if (!sh_bang) {
p = copy_strings(envc,envp,page,p,0);
p = copy_strings(argc,argv,page,p,0);
if (!p) {
retval = -ENOMEM;
goto exec_error2;
}
}
/* OK, This is the point of no return */
/*
前面我们根据函数参数提供的信息,对运行执行文件的命令行参数和环境空间进行了设置,但还
没有为执行文件做过什么实质性的工作,即还没有做过为执行文件初始化进程任务结构信息、建
立页表等工作。现在我们就来做这些工作。由于执行文件直接使用当前进程的“躯壳”,即当前
进程将被改造成执行文件的进程,因此我们需要首先释放当前进程占用的某些系统资源,包括关
闭指定的已打开文件、占用的页表和内存页面等。然后根据执行文件头结构信息修改当前进程使
用的局部描述符表 LDT 中描述符的内容,重新设置代码段和数据段描述符的限长,再利用前面处
理得到的 e_uid 和 e_gid 等信息来设置进程任务结构中相关的字段。最后把执行本次系统调用程
序的返回地址 eip[]指向执行文件中代码的起始位置处。这样当本系统调用退出返回后就会去运
行新执行文件的代码了。注意,虽然此时新执行文件的代码和数据还没有加载到内存中,但其参
数和环境块已经在 copy_strings()中使用 get_free_page()分配了物理内存页来保存数据,并在
change_ldt()函数中使用 put_page()存放到了进程逻辑空间的末端。另外,在 reate_tables()
中也会由于在用户栈上存放参数和环境指针表而引起缺页异常,从而内存管理程序也会就此为用
户栈空间映射物理内存页。
*/
// 这里我们首先放回进程原执行程序的 i 节点,并且让进程 executable 字段指向新执行文件的 i
// 节点。然后复位原进程的所有信号处理句柄,但对于 SIG_IGN 句柄无须复位。再根据设定的执行
// 时关闭文件句柄close_on_exec位图标志关闭指定的打开文件并复位该标志。
if (current->executable)
iput(current->executable);
current->executable = inode;
for (i=0 ; i<32 ; i++)
current->sigaction[i].sa_handler = NULL;
for (i=0 ; i<NR_OPEN ; i++)
if ((current->close_on_exec>>i)&1)
sys_close(i);
current->close_on_exec = 0;
// 然后根据当前进程指定的基地址和限长,释放原来程序的代码段和数据段所对应的内存页表指定
// 的物理内存页面及页表本身。此时新执行文件并没有占用主内存区任何页面,因此在处理器真正
// 运行新执行文件代码时就会引起缺页异常中断。此时内存管理程序即会执行缺页处理而为新执行
// 文件申请内存页面和设置相关页表项,并且把相关执行文件页面读入内存中。如果“上次任务使
// 用了协处理器”指向的是当前进程,则将其置空,并复位使用了协处理器的标志。
free_page_tables(get_base(current->ldt[1]),get_limit(0x0f));
free_page_tables(get_base(current->ldt[2]),get_limit(0x17));
if (last_task_used_math == current)
last_task_used_math = NULL;
current->used_math = 0;
// 然后我们根据新执行文件头结构中的代码长度字段 a_text 的值,来修改局部表中描述符基址和
// 段限长,并将 128KB 的参数和环境空间页面放置在数据段末端。执行下面语句之后p 此时更改
// 成以数据段起始处为原点的偏移值,但仍指向参数和环境空间数据开始处,即已转换成为栈指针
// 值。 然后调用内部函数 create_tables() 在栈空间中创建环境和参数变量指针表,供程序的
// main()作为参数使用,并返回该栈指针。
p += change_ldt(ex.a_text,page)-MAX_ARG_PAGES*PAGE_SIZE;
p = (unsigned long) create_tables((char *)p,argc,envc);
// 接着再修改进程各字段值为新执行文件的信息。即令进程任务结构代码尾字段 end_code 等于执
// 行文件的代码段长度 a_text数据尾字段 end_data 等于执行文件的代码段长度加数据段长度
// a_data + a_text并令进程堆结尾字段 brk = a_text + a_data + a_bss。 brk 用于指明
// 进程当前数据段(包括未初始化数据部分)末端位置,供内核为进程分配内存时指定分配开始位
// 置。然后设置进程栈开始字段为栈指针所在页面,并重新设置进程的有效用户 id 和有效组 id。
current->brk = ex.a_bss +
(current->end_data = ex.a_data +
(current->end_code = ex.a_text));
current->start_stack = p & 0xfffff000;
current->euid = e_uid;
current->egid = e_gid;
i = ex.a_text+ex.a_data;
while (i&0xfff)
put_fs_byte(0,(char *) (i++));
//直接进行缺页处理
end_ad=current->start_code+current->end_data;
for(address=current->start_code;address<=end_ad;address+=4096)
my_do_no_page(address);
// 最后将原调用系统中断的程序在堆栈上的代码指针替换为指向新执行程序的入口点,并将栈指针
// 替换为新执行文件的栈指针。此后返回指令将弹出这些栈数据并使得 CPU 去执行新执行文件,因
// 此不会返回到原调用系统中断的程序中去了。
eip[0] = ex.a_entry; /* eip, magic happens :-) */
eip[3] = p; /* stack pointer */
return 0;
exec_error2:
iput(inode);
exec_error1:
for (i=0 ; i<MAX_ARG_PAGES ; i++)
free_page(page[i]);
return(retval);
}