Linux0.11内核--加载可执行二进制文件之2.change_ldt

前面分析完了copy_strings函数,这里来分析另一个注意的函数change_ldt。

先来看调用处:

// 根据a_text 修改局部表中描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。
// 执行下面语句之后,p 此时是以数据段起始处为原点的偏移值,仍指向参数和环境空间数据开始处,
// 也即转换成为堆栈的指针。
  p += change_ldt (ex.a_text, page) - MAX_ARG_PAGES * PAGE_SIZE;

解释的很清楚,也就是说p指向的是相当于在图9-23的左方添加了64M-MAX_ARG_PAGES * PAGE_SIZE的大小容量。总容量为64M。

struct exec
{
  unsigned long a_magic;	/* 执行文件魔数。使用N_MAGIC 等宏访问。 */
  unsigned a_text;		/* 代码长度,字节数。 */
  unsigned a_data;		/* 数据长度,字节数。 */
  unsigned a_bss;		/* 文件中的未初始化数据区长度,字节数。 */
  unsigned a_syms;		/* 文件中的符号表长度,字节数。 */
  unsigned a_entry;		/* 执行开始地址。 */
  unsigned a_trsize;		/* 代码重定位信息长度,字节数。 */
  unsigned a_drsize;		/* 数据重定位信息长度,字节数。 */
};

// 下面对执行文件的头结构数据进行处理,首先让ex 指向执行头部分的数据结构。
  ex = *((struct exec *) bh->b_data);	/* read exec-header *//* 读取执行头部分 */

这里的ex为读取的可执行二进制文件头部分。下面进入change_ldt函数:

//// 修改局部描述符表中的描述符基址和段限长,并将参数和环境空间页面放置在数据段末端。
// 参数:text_size - 执行文件头部中a_text 字段给出的代码段长度值;
// page - 参数和环境空间页面指针数组。
// 返回:数据段限长值(64MB)。
static unsigned long
change_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--)
    {
      data_base -= PAGE_SIZE;
      if (page[i])		// 如果该页面存在,
	put_page (page[i], data_base);	// 就放置该页面。
    }
  return data_limit;		// 最后返回数据段限长(64MB)。
}

第一二行的意思是code_limit最少也要有一内存页的长度。

data_limit赋值为64M。

紧接着设置当前进程的LDT的代码段和数据段的基址和段限长。

注意最后一段比较关键,从数据段末尾data_base开始放置参数和环境空间已存放数据的页面。page是参数环境空间所有页面地址的数组。如果page[i]该页面存在,就调用put_page函数:

/*
* 下面函数将一内存页面放置在指定地址处。它返回页面的物理地址,如果
* 内存不够(在访问页表或页面时),则返回0。
*/
//// 把一物理内存页面映射到指定的线性地址处。
// 主要工作是在页目录和页表中设置指定页面的信息。若成功则返回页面地址。
// 在缺页异常的C 函数do_no_page()中会调用此函数。对于缺页引起的异常,由于任何缺页缘故而
// 对页表作修改时,并不需要刷新CPU 的页变换缓冲(或称Translation Lookaside Buffer,TLB),
// 即使页表项中标志P 被从0 修改成1。因为无效页项不会被缓冲,因此当修改了一个无效的页表项
// 时不需要刷新。在此就表现为不用调用Invalidate()函数。
unsigned long
put_page (unsigned long page, unsigned long address)
{
  unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */
/* 注意!!!这里使用了页目录基址_pg_dir=0 的条件 */

// 如果申请的页面位置低于LOW_MEM(1Mb)或超出系统实际含有内存高端HIGH_MEMORY,则发出警告。
  if (page < LOW_MEM || page >= HIGH_MEMORY)
    printk ("Trying to put page %p at %p\n", page, address);
// 如果申请的页面在内存页面映射字节图中没有置位,则显示警告信息。
  if (mem_map[(page - LOW_MEM) >> 12] != 1)
    printk ("mem_map disagrees with %p at %p\n", page, address);
// 计算指定地址在页目录表中对应的目录项指针。
  page_table = (unsigned long *) ((address >> 20) & 0xffc);
// 如果该目录项有效(P=1)(也即指定的页表在内存中),则从中取得指定页表的地址??page_table。
  if ((*page_table) & 1)
    page_table = (unsigned long *) (0xfffff000 & *page_table);
  else
    {
// 否则,申请空闲页面给页表使用,并在对应目录项中置相应标志7(User, U/S, R/W)。然后将
// 该页表的地址??page_table。
      if (!(tmp = get_free_page ()))
	return 0;
      *page_table = tmp | 7;
      page_table = (unsigned long *) tmp;
    }
// 在页表中设置指定地址的物理内存页面的页表项内容。每个页表共可有1024 项(0x3ff)。
  page_table[(address >> 12) & 0x3ff] = page | 7;
/* no need for invalidate */
/* 不需要刷新页变换高速缓冲 */
  return page;			// 返回页面地址。
}

首先通过data_base获取到相对应的页目录项指针,如果有效则获取页表地址。比较关键的是最后一句,address>>12表示data_base/4k,再与0x3ff(1024个项),因为这里不是字节而是索引,所以不是0xffc。然后用这个索引和page(参数和环境空间已存放数据的页面地址)绑定。

最后返回64M。

时间: 2024-10-26 15:42:42

Linux0.11内核--加载可执行二进制文件之2.change_ldt的相关文章

Linux0.11内核--加载可执行二进制文件之3.exec

最后剩下最核心的函数do_execve了,由于这里为了简单起见我不分析shell命令的情况, /* * 'do_execve()'函数执行一个新程序. */ //// execve()系统中断调用函数.加载并执行子进程(其它程序). // 该函数系统中断调用(int 0x80)功能号__NR_execve 调用的函数. // 参数:eip - 指向堆栈中调用系统中断的程序代码指针eip 处,参见kernel/system_call.s 程序 // 开始部分的说明:tmp - 系统中断调用本函数时

Linux0.11内核--加载二进制文件之1.copy_strings

从现在开始就是分析最后的核心模块exec.c了,分析完这个文件后,就会和之前的所有分析形成一个环路,从创建进程.加载进程程序到进程调度.内存管理. exec.c的核心do_execve函数很长,而且用到了很多其他的函数,copy_strings就是其中一个,我们这里就先来分析这个函数. 首先看调用处,在main.c中: static char *argv_rc[] = { "/bin/sh", NULL}; // 调用执行程序时参数的字符串数组. static char *envp_r

Linux-0.11内核源码分析系列:关于线性地址,逻辑地址,物理地址的关系与区别

/* *Author : DavidLin *Date : 2014-11-22pm *Email : [email protected] or [email protected] *world : the city of SZ, in China *Ver : 000.000.001 *history : editor time do * 1)LinPeng 2014-11-22 created this file! * 2) */     以下所有描述基于Linux0.11内核及其所编写的年

Linux0.11内核剖析–内核体系结构 &#169;Fanwu

Linux0.11内核剖析–内核体系结构 ©Fanwu <Linux内核完全注释>下载:http://files.cnblogs.com/files/HanBlogs/linux-kernel.pdf(进入pdf后要点击右下角保存喔^_^) 一个完整可用的操作系统主要由 4 部分组成:硬件.操作系统内核.操作系统服务和用户应用程序,如下图所示: 用户应用程序是指那些字处理程序. Internet 浏览器程序或用户自行编制的各种应用程序: 操作系统服务程序是指那些向用户所提供的服务被看作是操作系

一站式linux0.11内核head.s代码段图表详解

阅读本文章需要的基础: 计算机组成原理:针对8086,80386CPU架构的计算机硬件体系要有清楚的认知,我们都知道操作系统是用来管理硬件的,那我们就要对本版本的操作系统所依赖的硬件体系有系统的了解,有了系统的了解后才能全面的管理它,我们对8086,80386CPU架构的计算机硬件体系如果有非常深刻的认识,我们看源代码内核的时候,就可以更可能的以一种开发者的角度去思考代码的作用,先从全局的角度去思考问题,而不是采用一种众人摸象的思维从头看到末尾. 计算机编程C语言基础:linux内核基本都是用C

linux0.11内核fork实现分析(不看不知道,一看很简单)

pcDuino3下支持mmc启动,官方的Uboot是采用SPL框架实现的,因为内部的SRAM空间达到32K,我们完全可以在这32K空间内编写一个完整可用小巧的bootloader来完成引导Linux kernel的目的. 我们首先介绍下SPL框架,可以先看下<GNU ARM汇编--(十八)u-boot-采用nand_spl方式的启动方法>和<GNU ARM汇编--(十九)u-boot-nand-spl启动过程分析>,NAND_SPL也算是SPL框架下的一种模式. 当使用Nand f

Linux-0.11内核内存管理get_free_page()函数分析

/* *Author : DavidLin*Date : 2014-11-11pm*Email : [email protected] or [email protected]*world : the city of SZ, in China*Ver : 000.000.001*history : editor time do 1)LinPeng 2014-11-11 created this file! 2)*/Linux-0.11内存管理模块是源代码中比较难以理解的部分,现在把笔者个人的理解

程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26

程序的加载和执行(六)--<x86汇编语言:从实模式到保护模式>读书笔记26 通过本文能学到什么? NASM的条件汇编 用NASM编译的时候,通过命令行选项定义宏 Makefile的条件语句 在make命令行中覆盖Makefile中的变量值 第13章习题解答 复习如何构造栈段描述符 我们接着上篇博文说. 在我修改后的文件中,用到了条件汇编. 比如: %ifdef DEBUG put_core_salt: ;打印内核的符号 ... ... put_usr_salt: ;打印用户的符号 ... .

JavaScript 的性能优化:加载和执行

随着 Web2.0 技术的不断推广,越来越多的应用使用 JavaScript 技术在客户端进行处理,从而使 JavaScript 在浏览器中的性能成为开发者所面临的最重要的可用性问题.而这个问题又因 JavaScript 的阻塞特性变的复杂,也就是说当浏览器在执行 JavaScript 代码时,不能同时做其他任何事情.本文详细介绍了如何正确的加载和执行 JavaScript 代码,从而提高其在浏览器中的性能. 概览 无论当前 JavaScript 代码是内嵌还是在外链文件中,页面的下载和渲染都必