📄
字号:
<html><head><title>Linux原代码分析 </title></head><body><p><font FACE="宋体" SIZE="2"> </p><p></font><font face="宋体" size="7"> </font><font FACE="隶书" SIZE="6"> Linux代码分析实验报告 </font><font FACE="宋体" size="4"> ----关于EXECVE系统调用 </font><font FACE="宋体" SIZE="7"></p><p></font><font FACE="宋体" SIZE="5" COLOR="#0000ff"><i> 962班 <b>陈浩 </b>(学号:9630043)</i></font><font FACE="宋体" SIZE="5"></p><p>一 <font FACE="隶书" SIZE="6">简介(Introdution): <br></font> <strong> </font><font FACE="宋体" size="4">1</strong>,<strong>进程(process)</strong>: 在多道程序批处理系统和分时系统中,程序并不能独立运行。作为资源分配和运行的基本单位是进程。操做系统所具有的四大特征也都是基于进程而形成的。进程对于操做系统具有特别重要的意义。进程是程序的一个执行过程,其运动性质是由其自身的状态变化决定的。通常在操作系统中,进程至少有三种基本状态:运行态,就绪态,等待态。Linux 是一个多任务的分时操作系统</font><font FACE="宋体" SIZE="5">, </font><font FACE="宋体" size="4">在<strong>Linux </strong>中进程被分为如下状态<strong>:Running, Waiting, Stopped, Zombie.</strong> 四种状态。在Linux中,主要的进程的产生之间的关系如下所示:<br><br><br></font><font FACE="宋体" SIZE="5"> </font><font FACE="宋体" size="4">init(1)-+-crond(98)<br> |-emacs(387)<br> |-gpm(146) <br> |-inetd(110)<br> |-kerneld(18)<br> |-kflushd(2) <br> |-klogd(87)<br> |-kswapd(3) <br> |-login(160)---bash(192)---emacs(225)<br> |-lpd(121) <br> |-mingetty(161)<br> |-mingetty(162)<br> |-mingetty(163) <br> |-mingetty(164)<br> |-login(403)---bash(404)---pstree(594)<br> |-sendmail(134)<br> |-syslogd(78)<br> `-update(166)</font><font FACE="宋体" SIZE="5"><font FACE="宋体" SIZE="6"></p></font><p> </font><font FACE="宋体" size="4"><strong>2,进程和内存分配(memory management)的关系:</strong>因为一个程序必须首先被放在内存中才能运行,而且对每一进程都要有一相应的进程控制块(PCB),所以每一个进程都必须占用一定的存储空间。但因为真正的 physical memory 要比 virtual memory 要小的多,所以操作系统需要对 physical memory 进行最有效的利用。其中的一种办法就是 OS 只把那些正被使用着的页调进内存。这种只有在真正需要访问时才把虚页装进内存的技术被称为 demand paging.当一个进程试图访问一 virtual address 而它当前却不在内存中时,处理器将找不到所须的页表项。这时,处理器将通知操作系统发生了 page fault。<br> 如果此 faulting virtual address 是非法的,这就意味着此进程正试图访问一个不属于它的 virtual address,这表明进程已经出现错误,操作系统为保护其他系统中进程将终止此错误进程。<br> 如果 faulting virtual address 是合法的但所须的页当前不在内存中,操作系统会把此页从硬盘调进内存中。相对来说,对硬盘的访问需要很长时间,这样此进程就必须</font><font FACE="宋体" size="3">在所</font><font FACE="宋体" size="4">须页被调进内存之前等待相当长的一段时间,如果此时有其它的进程可以运行,操作系统将先让这些进程运行。新调进的页将被写进一个空闲的 physical page frame ,并且一个新的关于此 virtual page 的页表项被加进 processes page table。然后,此进程被选中以继续运行,运行是从 memory fault 发生时的机器指令开始进行下去的。 </font><font FACE="宋体" SIZE="5"></p><p></font><font FACE="宋体" size="4"> </font><font FACE="宋体" SIZE="5"> </font><font FACE="宋体" size="4">Linux 使用 demand paging 以把可执行的程序装入进程的 virtual memory。当一个命令被执行时,包含此命令的文件被打开并且它的内容被映射进进程的 virtual memory中。通过修改<strong><u> 描述这些进程的数据结构</u><em>(</font><font FACE="宋体" SIZE="5">memory mapping</font><font FACE="宋体" size="4">)</em></strong>,上面所说的映射是很容易做到的。然而,只有可执行文件的第一部分被真正的放进物理内存。文件的其它部分还是在磁盘上。当此文件(进程)执行时,它会造成 page faults 的出现,Linux 将根据进程的 memory map 决定需要把文件的哪一块调进内存以满足执行的需要。</font><font FACE="宋体" SIZE="5"></p><font FACE="宋体" SIZE="6"><p></font> </font><font FACE="宋体" size="4"> exec系统调用从一个指定的程序重新初始化一个进程,当该进程保持不变时程序却有可能改变。另一方面,fork系统调用是用复制指令,用户数据段和系统数据段的办法创建一个新进程,该进程是一个已存在进程的复制品,而不是从一个程序初始化得来的。 </font><font FACE="宋体" SIZE="5"><font FACE="宋体" SIZE="6"></p><p align="left"></font><font FACE="宋体" size="4"> 没有fork,exec的使用将受到限制;而没有 exec, fork也无任何实际使用意义。除了引导Linux 核心自身外,exec 系统调用是程续在Linux上获得执行的唯一方式。不仅shell使用 exec 执行用户的程序,而且 shell 和他的祖先也是通过 exec 引用的。而 fork 系统调用则是创建新进程的唯一方式。 </font><font FACE="宋体" SIZE="6"></p></font><p><strong>二 Principles</strong></p><p> <strong>(一) 结构</strong>:<br> </font><font FACE="宋体" size="4">函数 do_execve()主要完成以下步骤:<br> <em><strong>1</strong></em>.把页表清 0;<br> <em><strong>2</strong></em>.调用函数 open_namei(); <br> <em><strong>3</strong></em>.检查此系统调用本身的参数设置是否正确; <br> <em><strong>4</strong></em>.调用函数 prepare_binprm();此函数主要完成以下工作 :<br> </font><font face="宋体" size="5"> </font><font FACE="宋体" size="4">1.检查文件的类型是否满足执行条件; <br> 2.检查执行权限;<br> 3.检查文件此是否正在被写入(修改);<br> 4.set user_id, set group_id;<br> 5.调用 memset() 和 read_exec();<br> <em><strong>5.</strong></em>调用函数 copy_strings(); <br> <em><strong>6</strong></em>.调用 search_binary_handler(),</font><font FACE="宋体" size="3">查找可执行</font><font FACE="宋体" size="4">的文件格式; </font><font FACE="宋体" SIZE="5"> </font><font FACE="宋体" size="4"></p></font><font><p><strong><big><big>(二) 算法</big></big></font><font FACE="宋体" SIZE="6">:</font> </strong></p><blockquote> <blockquote> <blockquote> <font FACE="宋体" size="3"><p></font><font color="#00FF40" face="宋体" size="3"> </font><font face="宋体" size="3" color="#000000"> 由于 Linux 是用 comand-loading 算法,所以 do_execve() 实际所做的工作并不多,不需要把所要执行的程序真正的读入内存,而仅仅把所要的代码头设置好,并把进程的状态设置为“就绪”,等待系统的调度,以运行此进程。<br> 真正的把代码读入内存,要涉及到较复杂的算法,如在首先要内存中寻找一块足够大的空间,然后再把程序从外存读入此空间中。<br> exec()所做的和内存有关的工作有以下这些:<br> </font> <font face="宋体" size="3"> (1). 内存空间的获取: <br> 1 page for exec header entire file for omagic <br> 1 page or more for stack (MAX_ARG_PAGES) <br> (2). <tt>clear_page_tables()</tt> used to remove old pages. <br> <tt> (3). change_ldt()</tt> sets the descriptors in the new <tt>LDT[]</tt> <br> <tt> </tt> (4). <tt>ldt[1]</tt> = code base=0x00, limit=TASK_SIZE <br> <tt> </tt> (5). <tt>ldt[2]</tt> = data base=0x00, limit=TASK_SIZE<br> These segments are DPL=3, P=1, S=1, G=1. type=a (code) or 2 (data) <br> (6). Up to <tt>MAX_ARG_PAGES</tt> dirty pages of argv and envp are allocated and stashed at the top of the data segment for the newly created user stack.</font></p> <p><font FACE="宋体" size="3"> (7). Set the instruction pointer of the caller <tt>eip = ex.a_entry</tt> <br> (8). Set the stack pointer of the caller to the stack just created (esp = stack pointer) These will be popped off the stack when the caller Resumes. <br> (9). update memory limits<br> <tt> end_code = ex.a_text</tt><br> <tt> end_data = end_code + ex.a_data</tt><br> <tt> brk = end_data + ex.a_bss</tt> </p> </font> </blockquote> </blockquote> <blockquote> <font FACE="宋体" size="3"><p>Interrupts and traps 是在当前任务的环境中被处理的。特别的,地址的转换是用当前任务的页目录进行的。然而,段却是用的内核的。这样,所有的线形地址都指向 kernel memory。<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -