本次实践项目有两个基本内容:
(1)用Bochs调试工具跟踪Linux-0.11的地址转换过程;
(2)实现基于共享物理页框的进程间内存共享。
和一个段有关的信息需要 8 个字节来描述,所以称为段描述符(Segment Descriptor),每个段都需要一个描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里,所有的描述符都是挨在一起,集中存放的,这就构成一个描述符表。最主要的描述符表是全局描述符表(Global Descriptor Table, GDT)。为了跟踪全局描述符表,处理器内部有一个 48 位的寄存器,称为全局描述符表寄存器(GDTR),该寄存器分为两部分,分别是 32 位的线性地址和 16 位的边界。



在保护模式下访问一个段时,传送到段选择器的是段选择子。它由三部分组成,第一部分是描述符的索引号,用来在描述符表中选择一个段描述符。 TI 是描述符表指示器(Table Indicator), TI=0 时,表示描述符在 GDT 中; TI=1 时,描述符在 LDT 中。 RPL 是请求特权级,表示给出当前选择子的那个程序的特权级别,正是该程序要求访问这个内存段。
要跟踪地址转换过程,首先需要以汇编级调试的方式启动Bochs,即在编译好Linux-0.11后,通过运行命令./dbg-asm来启动调试器,此时Bochs模拟器会处于黑屏状态,执行命令的宿主主机窗口中的显示如下图所示:

"Next at t=0"表示下面执行的指令是Bochs启动后要执行的第一条指令,单步跟踪进去就能看到BIOS代码。现在直接输入命令“c”,即继续运行程序,Bochs和以前一样启动Linux-0.11。
现在需要在Linux-0.11上编写一个测试程序test.c,要跟踪的地址就是这个程序中的地址。test.c代码如下:
#include <stdio.h>
int i = 0x12345678;
int main(void)
{
printf("The logical address of i is 0x%08x",&i);
fflush(stdout);
while(i);
return 0;
}
将test.c拷贝到Linux-0.11上编译、运行,运行输出如下:
The logical address of i is 0x00003004
由于打印的是逻辑地址,即离开程序段首的偏移地址,所以只要程序test.c不发生变化,0x00003004这个值也是不会变化的,即在同一机器上多次运行test.c,这个逻辑地址也是一样的。
由于test.c中有一个死循环,所以这个程序不会主动退出,正是这样,其各种资源,如逻辑地址、LDT表、GDT表、页表等信息才能在调试器中用调试命令查看。
现在在Bochs命令行窗口按下Ctrl + c键,Bochs会暂停运行,进入调试状态。此时的Bochs会有很大的可能是在test.c中运行,因为此时Linux-0.11中进程很少。宿主机调试器窗口会显示类似如下信息:

若其中的"000f"显示为"0008",则说明按下Ctrl+c中断发生在内核中,这时需要输入c继续执行,然后再按下Ctrl+c直到变为"000f"为止。如果显示的指令不是cmp,就用"n"命令单步运行几步,直到停在cmp指令上,实际上就是停在while(i)语句处。然后用“u/8命令”,显示从当前位置开始的8条指令的反汇编代码,如下图所示:

这正是从while(i)开始到return语句的汇编代码。不难分析出,变量i就保存在地址DS:0x3004处。cmp指令要将DS:3004处存放的内容和0就行比较,只有等于0才跳出循环,即执行"jz .+0x00000004"。
现在要开始寻找逻辑地址DS:0x3004对应的物理地址,即开始跟踪地址转换过程。由于是段页式内存结构,所以要先用段表找到虚拟地址。DS:0x3004是逻辑地址,DS表明这个地址属于DS段,只有找到进程对应的段表以后,才能通过DS寄存器的值在段表中找到DS段的具体信息,得到虚拟地址。这个段表就是进程的LDT表,接下来就要找到这个LDT表,LDTR就是起点。LDTR寄存器中存放的是当前进程LDT表地址在GDT表中的偏移值。
用"sreg"命令可以看到各个寄存器的信息:

可以看到ldtr的值是0x0068 = 0000000001101000,根据段选择子的结构,当前进程的LDT存放在GDT表中的13(1101)号位置。
GDT表的位置由GDTR寄存器明确给出,即在物理地址0x00005cb8位置处,GDT表中每一项占8个字节,所以我们要查找的LDT表的物理地址是0x00005cb8 + 13 * 8。用命令“xp /2w 0x00005cb8+13*8”可以查看这个位置的内容:

这两步在不同的机器上执行时得到的数值可能不一样,这是正常的。如果向确认是否正确,就看执行sreg命令后的输出信息中,ldtr所在行里的dl和dh的值,它们是Bachs自动计算出来的,从GDT表中找到的LDT地址应该和Bochs计算出来的一致。
将得到的数字“0xa2d00068 0x000082f9”进行组合,组合方式如下,得到LDT表的物理地址0x00f9a2d0,这就是LDT表的物理地址,组合方式是由段描述符的格式决定的。
执行命令“xp /8w 0x00f9a2d0”可以得到:

这就是当前进程LDT表的前四项内容了。
现在可以根据DS寄存器来查找LDT表了,由上面"sreg"命令获得的寄存器信息“ds:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=3”,可以直到,DS寄存器的值是0x0017,按照段选择子的格式,0x0017 = 0x0000000000010111,去掉最低三位,剩余的位组合起来得到的数值是2,即偏移为,因此是第三项,即“0x00003fff 0x10c0f300”,这个就是DS段的信息。用同样的组合方式组合“0x00003fff 0x10c0f300”得到DS段的基址为0x10000000,这就是当前进程DS段在虚拟内存空间中的起始地址。因此DS:0x3004对应的虚拟地址为:
0x10000000+3004 = 0x10003004
现在已经得到了虚拟地址,接下来就要将其转换为物理地址,核心就是查找页表。首先要计算出虚拟地址中的页目录号、页表号和页内偏移,它们分别对应了虚拟地址的前10位、中间10位和末尾的12位。不难计算出,虚拟地址0x10003004对应的页目录号是64,页号是3,页内偏移是4。页目录表的位置由CR3寄存器给出,用“creg”命令可以看到:

说明页目录表的基址为0。页目录表和表中内容都很简单,就是1024个32位二进制树,这32位中的前20位表示物理页框号,后面是一些属性信息(其中最重要的是最后一位P,表示是否有效)。第65页目录项就是要找的内容,用命令“xp /w 0+644”查看:

其中的027是属性,显然P=1,因此这个页目录项是有效的。因此页表所在物理页框号位0x00fa9,即该页目录对应的1024个页的所有页表项信息存放在物理地址0x00fa9000处,从该位置开始查找第3个页表项,即“xp /w 0x00fa9000+34”:

其中的067是属性,显然P=1,说明页表项也是有效的。
现在已知虚拟地址0x10003004对应的物理页框号为0x00fa7000,将它和页内偏移0x0004连接到一起,得到物理地址为0x00fa7004,这个就是变量i的物理地址,用命令“xp /w 0x00fa7004”查看:

得到的数值就是变量i的值,说明这个过程是正确的。
现在直接修改内存来改变i的值,使用命令“setpmem 0x00fa7004 4 0”实现,表示从0x00fa7004地址开始的4个字节都设置为0,然后使用“c”命令继续Bochs的运行,可以看到test进程退出了,说明i变量修改成功了。
在Linux下,可以通过shmget()和shmat()两个系统调用来使用共享内存。因此本部分的具体实现内容就是在Linux-0.11下添加shmget()和shmat()两个系统调用(Linux-0.11上没有这两个系统调用)。添加系统调用的具体过程可以参照操作系统实验2:系统调用。
shmget()系统调用的函数原型为:
int shmget(key_t key, size_t size, int shmflg);
该系统调用会新建/打开一页物理内存作为共享内存,并返回该页共享内存的shmid,即该页共享内存在操作系统中的标识。如果多个进程使用相同的key调用shmget,则这些进程就会获得相同的shmid,即得到同一块内存的标识。在shmget实现时,如果key所对应的共享内存已经建立,则直接返回shmid,否则新建。如果size超过一页内存的大小,返回-1,并置errno为EINVAL。如果系统无空=空闲内存,返回-1,并置errno为ENOMEM。对于本实验,shmflg参数忽略。
shmat()系统调用的函数原型为:
void* shmat(int shmid,const void *shmaddr, int shmflg);
该系统调用会将shmid指定的共享页面映射到当前进程的虚拟地址空间中,并返回一个逻辑地址p,调用进程可以通过读写逻辑地址p来读写这一页共享内存。如果shmid非法,返回-1,并置errno为EINVAL。对于本实验,参数shmaddr和shmflg都忽略。
因此,两个进程都调用shmat可以关联到同一页内存上,此时两个进程读写p指针就是在读写同一页内存,从而实现了基于共享内存的进程间通信。
下面参照操作系统实验2:系统调用添加系统调用。
添加系统调用的编号
系统调用编号在 include/unistd.h中定义,打开该文中找到系统调用编号的定义:

添加IDT(中断描述符表)
打开include/linux/sys.h文件,在文件中的sys_call_table[]的数组中添加sys_shmget和sys_shmat,注意这里的前后顺序要和之前的系统调用编号的前后关系对应起来。同时,将sys_shmget()和sys_shmat()声明全局函数。

修改系统调用数
修改kernel/system_call.s中的系统调用数,由原来的72改为74.
实现sys_shmget()和sys_shmat()
在kernel/目录下新建一个shm.c文件,用于保存这个两个函数。
在include/目录下新建一个shm.h文件,用于相关数据类型声明和函数声明。
sys_shmget()函数的主要作用是获得一个空闲的物理页面,可以通过调用已有的get_free_page()函数来实现。

sys_shmat()的主要作用是将这个页面和进程的虚拟地址以及逻辑地址关联起来,让进程对某个逻辑地址的读写就是在读写该内存页。该函数首先要完成虚拟地址和物理页面的映射,核心就是填写页表,在Linux-0.11中的函数:
unsigned long put_page(unsigned long page,unsigned long address)
该函数的作用就是完成这样的映射,直接调用即可。函数中的page就是内存页的物理地址,即shm_list[shmid].page,address是虚拟地址。Linux-0.11给每个进程分配了64M的虚拟内存,其分布如下图所示。

可以看出,brk和start_stack之间的虚拟内存并没有使用,因此可以在这里分割一个虚拟内存页和那个物理内存页建立映射。brk和start_stack都是存储在进程的PCB中,可以用current->brk找到当前进程的brk,当前进程开始的虚拟地址存放在current->ldt[1]中,可以用get_base(current->ldt[1])获得,因此该虚拟内存页的虚拟地址为get_base(current->ldt[1]) + current->brk.
这样调用put_page函数就可以建立映射关系了。最后需要更新brk指针的指向,并返回虚拟内存页的逻辑地址,即原来的brk。
shm.h文件的内容如下:
#include <stddef.h>
typedef unsigned int key_t;
struct struct_shmem
{
unsigned int size;
unsigned int key;
unsigned long page;
};
int shmget(key_t key, size_t size);
void* shmat(int shmid);
#define SHM_NUM 16
shm.c文件内容如下:
#include <shm.h>
#include <linux/mm.h>
#include <unistd.h>
#include <errno.h>
#include <linux/kernel.h>
#include <linux/sched.h>
struct struct_shmem shm_list[SHM_NUM] = {{0,0,0}};
int sys_shmget(key_t key, size_t size)
{
int i;
unsigned long page;
if(size > PAGE_SIZE){
errno = EINVAL;
printk("shmget:The size connot be greater than the PAGE_SIZE!\r\n");
return -1;
}
if(key == 0){
printk("shmget:key connot be 0!\r\n");
return -1;
}
//判斷是否已经创建
for(i = 0; i < SHM_NUM; i++){
if(shm_list[i].key == key)
return i;
}
page = get_free_page(); //申请内存页
if(!page){
errno = ENOMEM;
printk("shmget:connot get free page!\r\n");
return -1;
}
for(i = 0; i < SHM_NUM; i++){
if(shm_list[i].key == 0){
shm_list[i].size = size;
shm_list[i].key = key;
shm_list[i].page = page;
break;
}
}
return i;
}
void* sys_shmat(int shmid)
{
unsigned long tmp; //虚拟地址
unsigned long logicalAddr;
if(shmid < 0 || shmid >= SHM_NUM || shm_list[shmid].page == 0 || shm_list[shmid].key <= 0){
errno = EINVAL;
printk("shmat:The shmid id invalid!\r\n");
return NULL;
}
tmp = get_base(current->ldt[1]) + current->brk; //计算虚拟地址
put_page(shm_list[shmid].page,tmp);
logicalAddr = current->brk; //记录逻辑地址
current->brk += PAGE_SIZE; //更新brk指针
return (void *)logicalAddr;
}
修改Makefile文件
文件位置:kernel/Makefile


修改完成之后重新编译整个工程。
编写测试代码
这里编写两个测试进程,一个进程向共享内存页中写入数据,另一进程从中读取数据,比对写入和读出的数据就能验证实验过程是否正确。
test1.c每间隔5秒向共享内存页中写入递增的数据,代码如下:
#define __LIBRARY__
#include <shm.h>
#include <unistd.h>
static inline _syscall1(void*,shmat,int,shmid);
static inline _syscall2(int,shmget,key_t,key,size_t,size);
int main()
{
key_t key = 666;
size_t size = sizeof(int);
int shmid = shmget(key,size);
int* p = (int*)shmat(shmid);
*p = 0;
while(1){
(*p)++;
printf("process1:write %d\r\n",*p);
sleep(5);
}
return 0;
}
test2.c每间隔5妙从共享内存页中读取数据,代码如下:
#define __LIBRARY__
#include <shm.h>
#include <unistd.h>
static inline _syscall1(void*,shmat,int,shmid);
static inline _syscall2(int,shmget,key_t,key,size_t,size);
int main()
{
key_t key = 666;
size_t size = sizeof(int);
int shmid = shmget(key,size);
int* p = (int*)shmat(shmid);
while(1){
printf("process2:read %d\r\n",*p);
sleep(5);
}
return 0;
}
sudo ./mount-hdc
然后将test1.c和test2.c拷贝到linux-0.11中,命令如下:
cp test1.c test2.c ./hdc/usr/root/
另外,在test1.c和test2.c中用到shm.h和修改过的unistd.h,因此也需要将这两个文件拷贝到Linux-0.11中,命令如下(在lab6目录下):
cp ./linux-0.11/include/unistd.h ./hdc/usr/include/
cp ./linux-0.11/include/shm.h ./hdc/usr/include/
gcc -o test1 test1.c
gcc -o test2 test2.c
sync
注意,Linux-0.11只有一个终端,而现在需要在一个终端上同时运行两个程序,方法是在命令末尾输入&,命令就会进入后台运行。命令如下:
./test1 &
./test2 &
然后就可看到打印出来的信息了,如下图所示:

可以看出,写入和读出的数据是一样的。
注意,在实验过程发现,如果一个进程结束会将共享的物理内存页释放,此时如果另一个进程再去读写该物理内存页就会导致错误!
至此,整个实验结束!
我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc
我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption
我正在我的Rails项目中安装Grape以构建RESTfulAPI。现在一些端点的操作需要身份验证,而另一些则不需要身份验证。例如,我有users端点,看起来像这样:moduleBackendmoduleV1classUsers现在如您所见,除了password/forget之外的所有操作都需要用户登录/验证。创建一个新的端点也没有意义,比如passwords并且只是删除password/forget从逻辑上讲,这个端点应该与用户资源。问题是Grapebefore过滤器没有像except,only这样的选项,我可以在其中说对某些操作应用过滤器。您通常如何干净利落地处理这种情况?
在我做的一些网络开发中,我有多个操作开始,比如对外部API的GET请求,我希望它们同时开始,因为一个不依赖另一个的结果。我希望事情能够在后台运行。我找到了concurrent-rubylibrary这似乎运作良好。通过将其混合到您创建的类中,该类的方法具有在后台线程上运行的异步版本。这导致我编写如下代码,其中FirstAsyncWorker和SecondAsyncWorker是我编写的类,我在其中混合了Concurrent::Async模块,并编写了一个名为“work”的方法来发送HTTP请求:defindexop1_result=FirstAsyncWorker.new.async.
因为我现在正在做一些时间测量,我想知道是否可以在不使用Benchmark类或命令行实用程序time的情况下测量用户时间或系统时间。使用Time类只显示挂钟时间,而不显示系统和用户时间,但是我正在寻找具有相同灵active的解决方案,例如time=TimeUtility.now#somecodeuser,system,real=TimeUtility.now-time原因是我有点不喜欢Benchmark,因为它不能只返回数字(编辑:我错了-它可以。请参阅下面的答案。)。当然,我可以解析输出,但感觉不对。*NIX系统的time实用程序也应该可以解决我的问题,但我想知道是否已经在Ruby中实
a=[3,4,7,8,3]b=[5,3,6,8,3]假设数组长度相同,是否有办法使用each或其他一些惯用方法从两个数组的每个元素中获取结果?不使用计数器?例如获取每个元素的乘积:[15,12,42,64,9](0..a.count-1).eachdo|i|太丑了...ruby1.9.3 最佳答案 使用Array.zip怎么样?:>>a=[3,4,7,8,3]=>[3,4,7,8,3]>>b=[5,3,6,8,3]=>[5,3,6,8,3]>>c=[]=>[]>>a.zip(b)do|i,j|c[[3,5],[4,3],[7,6],
如果names为nil,则以下中断。我怎样才能让这个map只有在它不是nil时才执行?self.topics=names.split(",").mapdo|n|Topic.where(name:n.strip).first_or_create!end 最佳答案 其他几个选项:选项1(在其上执行map时检查split的结果):names_list=names.try(:split,",")self.topics=names_list.mapdo|n|Topic.where(name:n.strip).first_or_create!e