📄 exec.c
字号:
/** linux/fs/exec.c** (C) 1991 Linus Torvalds*//** #!-checking implemented by tytso.*//** #!开始的程序检测部分是由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.*//** 需求时加载是于1991.12.1 实现的 - 只需将执行文件头部分读进内存而无须* 将整个执行文件都加载进内存。执行文件的i 节点被放在当前进程的可执行字段中* ("current->executable"),而页异常会进行执行文件的实际加载操作以及清理工作。** 我可以再一次自豪地说,linux 经得起修改:只用了不到2 小时的工作时间就完全* 实现了需求加载处理。*/#include <errno.h> // 错误号头文件。包含系统中各种出错号。(Linus 从minix 中引进的)。#include <string.h> // 字符串头文件。主要定义了一些有关字符串操作的嵌入函数。#include <sys/stat.h> // 文件状态头文件。含有文件或文件系统状态结构stat{}和常量。#include <a.out.h> // a.out 头文件。定义了a.out 执行文件格式和一些宏。#include <linux/fs.h> // 文件系统头文件。定义文件表结构(file,buffer_head,m_inode 等)。#include <linux/sched.h> // 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。#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 !*//** MAX_ARG_PAGES 定义了新程序分配给参数和环境变量使用的内存最大页数。* 32 页内存应该足够了,这使得环境和参数(env+arg)空间的总合达到128kB!*/#define MAX_ARG_PAGES 32/** 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.*//** create_tables()函数在新用户内存中解析环境变量和参数字符串,由此* 创建指针表,并将它们的地址放到"堆栈"上,然后返回新栈的指针值。*///// 在新用户堆栈中创建环境和参数变量指针表。// 参数:p - 以数据段为起点的参数和环境信息偏移指针;argc - 参数个数;envc -环境变量数。// 返回:堆栈指针。static unsigned long *create_tables (char *p, int argc, int envc){ unsigned long *argv, *envp; unsigned long *sp;// 堆栈指针是以4 字节(1 节)为边界寻址的,因此这里让sp 为4 的整数倍。 sp = (unsigned long *) (0xfffffffc & (unsigned long) p);// sp 向下移动,空出环境参数占用的空间个数,并让环境参数指针envp 指向该处。 sp -= envc + 1; envp = sp;// sp 向下移动,空出命令行参数指针占用的空间个数,并让argv 指针指向该处。// 下面指针加1,sp 将递增指针宽度字节值。 sp -= argc + 1; argv = sp;// 将环境参数指针envp 和命令行参数指针以及命令行参数个数压入堆栈。 put_fs_long ((unsigned long) envp, --sp); put_fs_long ((unsigned long) argv, --sp); put_fs_long ((unsigned long) argc, --sp);// 将命令行各参数指针放入前面空出来的相应地方,最后放置一个NULL 指针。 while (argc-- > 0) { put_fs_long ((unsigned long) p, argv++); while (get_fs_byte (p++)) /* nothing */ ; // p 指针前移4 字节。 } put_fs_long (0, argv);// 将环境变量各指针放入前面空出来的相应地方,最后放置一个NULL 指针。 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*//** count()函数计算命令行参数/环境变量的个数。*///// 计算参数个数。// 参数:argv - 参数指针数组,最后一个指针项是NULL。// 返回:参数个数。static intcount (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.*//** 'copy_string()'函数从用户内存空间拷贝参数和环境字符串到内核空闲页面内存中。* 这些已具有直接放到新用户内存中的格式。** 由TYT(Tytso)于1991.12.24 日修改,增加了from_kmem 参数,该参数指明了字符串或* 字符串数组是来自用户段还是内核段。** from_kmem argv * argv *** 0 用户空间 用户空间* 1 内核空间 用户空间* 2 内核空间 内核空间** 我们是通过巧妙处理fs 段寄存器来操作的。由于加载一个段寄存器代价太大,所以* 我们尽量避免调用set_fs(),除非实在必要。*///// 复制指定个数的参数字符串到参数和环境空间。// 参数:argc - 欲添加的参数个数;argv - 参数指针数组;page - 参数和环境空间页面指针数组。// p -在参数表空间中的偏移指针,始终指向已复制串的头部;from_kmem - 字符串来源标志。// 在do_execve()函数中,p 初始化为指向参数表(128kB)空间的最后一个长字处,参数字符串// 是以堆栈操作方式逆向往其中复制存放的,因此p 指针会始终指向参数字符串的头部。// 返回:参数和环境空间当前头部指针。static unsigned longcopy_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 *//* 偏移指针验证 */// 取ds 寄存器值到new_fs,并保存原fs 寄存器值到old_fs。 new_fs = get_ds (); old_fs = get_fs ();// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)。 if (from_kmem == 2) set_fs (new_fs);// 循环处理各个参数,从最后一个参数逆向开始复制,复制到指定偏移地址处。 while (argc-- > 0) {// 如果字符串在用户空间而字符串数组在内核空间,则设置fs 段寄存器指向内核数据段(ds)。 if (from_kmem == 1) set_fs (new_fs);// 从最后一个参数开始逆向操作,取fs 段中最后一参数指针到tmp,如果为空,则出错死机。 if (!(tmp = (char *) get_fs_long (((unsigned long *) argv) + argc))) panic ("argc is wrong");// 如果字符串在用户空间而字符串数组在内核空间,则恢复fs 段寄存器原值。 if (from_kmem == 1) set_fs (old_fs);// 计算该参数字符串长度len,并使tmp 指向该参数字符串末端。 len = 0; /* remember zero-padding */ do { /* 我们知道串是以NULL 字节结尾的 */ len++; } while (get_fs_byte (tmp++));// 如果该字符串长度超过此时参数和环境空间中还剩余的空闲长度,则恢复fs 段寄存器并返回0。 if (p - len < 0) { /* this shouldn't happen - 128kB */ set_fs (old_fs); /* 不会发生-因为有128kB 的空间 */ return 0; }// 复制fs 段中当前指定的参数字符串,是从该字符串尾逆向开始复制。 while (len) { --p; --tmp; --len;// 函数刚开始执行时,偏移变量offset 被初始化为0,因此若offset-1<0,说明是首次复制字符串,// 则令其等于p 指针在页面内的偏移值,并申请空闲页面。 if (--offset < 0) { offset = p % PAGE_SIZE;// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值。 if (from_kmem == 2) set_fs (old_fs);// 如果当前偏移值p 所在的串空间页面指针数组项page[p/PAGE_SIZE]==0,表示相应页面还不存在,// 则需申请新的内存空闲页面,将该页面指针填入指针数组,并且也使pag 指向该新页面,若申请不// 到空闲页面则返回0。 if (!(pag = (char *) page[p / PAGE_SIZE]) && !(pag = (char *) page[p / PAGE_SIZE] = (unsigned long *) get_free_page ())) return 0;// 如果字符串和字符串数组来自内核空间,则设置fs 段寄存器指向内核数据段(ds)。 if (from_kmem == 2) set_fs (new_fs); }// 从fs 段中复制参数字符串中一字节到pag+offset 处。 *(pag + offset) = get_fs_byte (tmp); } }// 如果字符串和字符串数组在内核空间,则恢复fs 段寄存器原值。 if (from_kmem == 2) set_fs (old_fs);// 最后,返回参数和环境空间中已复制参数信息的头部偏移值。 return p;}//// 修改局部描述符表中的描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。// 参数:text_size - 执行文件头部中a_text 字段给出的代码段长度值;// page - 参数和环境空间页面指针数组。// 返回:数据段限长值(64MB)。static unsigned longchange_ldt (unsigned long text_size, unsigned long *page){ unsigned long code_limit, data_limit, code_base, data_base; int i;// 根据执行文件头部a_text 值,计算以页面长度为边界的代码段限长。并设置数据段长度为64MB。 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 *//* 要确信fs 段寄存器已指向新的数据段 */// fs 段寄存器中放入局部表数据段描述符的选择符(0x17)。 __asm__ ("pushl $0x17\n\tpop %%fs"::);// 将参数和环境空间已存放数据的页面(共可有MAX_ARG_PAGES 页,128kB)放到数据段线性地址的// 末端。是调用函数put_page()进行操作的(mm/memory.c, 197)。 data_base += data_limit; for (i = MAX_ARG_PAGES - 1; i >= 0; i--) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -