案例网站有哪些,科技公司图片,新闻单位建设网站的意义,好品质自适应网站建设回忆我们需要做的事情#xff1a; 为了支持 shell 程序的执行#xff0c;我们需要提供#xff1a; 1.缺页中断(不理解为什么要这个东西#xff0c;只是闪客说需要#xff0c;后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的#xff0c;所以需要这两个东…回忆我们需要做的事情 为了支持 shell 程序的执行我们需要提供 1.缺页中断(不理解为什么要这个东西只是闪客说需要后边再说) 2.硬盘驱动、文件系统 (shell程序一开始是存放在磁盘里的所以需要这两个东西) 3.fork,execve, wait 这三个系统调用也可以说是 进程调度 (否则无法 halt shell 程序并且启动另外的程序) 4.键盘驱动、VGA/console/uart 驱动、中断处理 (支持键盘输入和屏幕显示) 5.内存管理 (shell 启动其它进程时不能共用内存而是切换其它进程的页表) 6.为了写代码方便我们需要从 MBR 进入到 main 函数这也是从 汇编 切换到 C 语言 — 已经完成 7.应用程序申请内存的接口
现在已经进入 main 函数了那么进入 main 函数后我们要怎么实现上面提到的还没完成的 6 个要求呢我们要实现它们才能启动 shell、
用户空间需要有内存管理机制。同样的内核空间的内存也需要管理比如我们需要给磁盘分配高速缓存为了方便管理内核空间内存我们会去实现如 kfree 和 kalloc 之类的内核函数。
继续看闪客文章第12回
https://mp.weixin.qq.com/s?__bizMzk0MjE3NDE0Ngmid2247500061idx1sn6cb3382d7ac35ebeac52bbba3a89db4echksmc2c5bbb0f5b232a6e1b2c7b1f55a7b7057d29ea11348068e122a03b75db220ffe19ea1e8fc24scene178cur_album_id2123743679373688834#rd
书接上回上回书咱们回顾了一下 main.c 函数之前我们做的全部工作给进入 main 函数做了一个充分的准备。 那今天我们就话不多说从 main 函数的第一行代码开始读。
还是把 main 的全部代码都先写出来很少。
void main(void) {ROOT_DEV ORIG_ROOT_DEV;drive_info DRIVE_INFO;memory_end (120) (EXT_MEM_K10);memory_end 0xfffff000;if (memory_end 16*1024*1024)memory_end 16*1024*1024;if (memory_end 12*1024*1024) buffer_memory_end 4*1024*1024;else if (memory_end 6*1024*1024)buffer_memory_end 2*1024*1024;elsebuffer_memory_end 1*1024*1024;main_memory_start buffer_memory_end;mem_init(main_memory_start,memory_end);trap_init();blk_dev_init();chr_dev_init();tty_init();time_init();sched_init();buffer_init(buffer_memory_end);hd_init();floppy_init();sti();move_to_user_mode();if (!fork()) { /* we count on this going ok */init();}for(;;) pause();
}我们今天就看这第一小段。
首先ROOT_DEV 为系统的根文件设备号DRIVE_INFO 为之前 setup.s 程序获取并存储在内存 0x90000 处的设备信息我们先不管这俩等之后用到了再说。
我们看后面这一坨很影响整体画风的一段代码。
void main(void) {...memory_end (120) (EXT_MEM_K10);memory_end 0xfffff000;if (memory_end 16*1024*1024)memory_end 16*1024*1024;if (memory_end 12*1024*1024) buffer_memory_end 4*1024*1024;else if (memory_end 6*1024*1024)buffer_memory_end 2*1024*1024;elsebuffer_memory_end 1*1024*1024;main_memory_start buffer_memory_end;...
}这一坨代码和后面规规整整的 xxx_init 平级的位置要是我们这么写代码肯定被老板批评被同事鄙视了。但 Linus 写的就是经典学就完事了。
这一坨代码虽然很乱但仔细看就知道它只是为了计算出三个变量罢了。
main_memory_start memory_end buffer_memory_end
而观察最后一行代码发现其实两个变量是相等的所以其实仅仅计算出了两个变量。
main_memory_start memory_end
然后再具体分析这个逻辑其实就是一堆 if else 判断而已判断的标准都是 memory_end 也就是内存最大值的大小而这个内存最大值由第一行代码可以看出是等于 1M 扩展内存大小。(即内存最小得有 1M)
那 ok 了其实就只是针对不同的内存大小设置不同的边界值罢了为了理解它我们完全没必要考虑这么周全就假设总内存一共就 8M 大小吧。
那么如果内存为 8M 大小memory_end 就是
8 * 1024 * 1024
也就只会走倒数第二个分支那么 buffer_memory_end 就为
2 * 1024 * 1024
那么 main_memory_start 也为
2 * 1024 * 1024
那这些值有什么用呢一张图就给你说明白了。 (我们之前把 system 放在 0x0把栈指针放在 0x9FF00所以可以认为内核程序占用内存为 1M) 你看其实就是定了三个箭头所指向的地址的三个边界变量具体主内存区是如何管理和分配的要看下面代码的功劳。
void main(void) {...mem_init(main_memory_start, memory_end);...
}而缓冲区是如何管理和分配的就要看
void main(void) {...buffer_init(buffer_memory_end);...
}是如何折腾的了。
那我们今天就不背着这两个负担了仅仅需要知道这三个参数的计算以及后面是为谁效力的就好啦是不是很轻松后面我们再讲如何利用这三个参数来做到内存的管理。
预知后事如何且听下会分解。
看闪客文章 “操作系统就用一张大表管理内存”
今天我们不聊具体内存管理的算法我们就来看看操作系统用什么样的一张表达到了管理内存的效果。
我们以 Linux 0.11 源码为例发现进入内核的 main 函数后不久有这样一坨代码。
void main(void) {...memory_end (120) (EXT_MEM_K10);memory_end 0xfffff000;if (memory_end 16*1024*1024)memory_end 16*1024*1024;if (memory_end 12*1024*1024) buffer_memory_end 4*1024*1024;else if (memory_end 6*1024*1024)buffer_memory_end 2*1024*1024;elsebuffer_memory_end 1*1024*1024;main_memory_start buffer_memory_end;mem_init(main_memory_start,memory_end);...
}除了最后一行外前面的那一大坨的作用很简单。
其实就只是针对不同的内存大小设置不同的边界值罢了为了理解它我们完全没必要考虑这么周全就假设总内存一共就 8M 大小吧。
那么如果内存为 8M 大小memory_end 就是
8 * 1024 * 1024
也就只会走倒数第二个分支那么 buffer_memory_end 就为
2 * 1024 * 1024
那么 main_memory_start 也为
2 * 1024 * 1024
你仔细看看代码逻辑看是不是这样
当然你不愿意细想也没关系上述代码执行后就是如下效果而已。 你看其实就是定了三个箭头所指向的地址的三个边界变量。具体主内存区是如何管理和分配的要看 mem_init 里做了什么。
void main(void) {...mem_init(main_memory_start, memory_end);...
}而缓冲区是如何管理和分配的就要看再后面的 buffer_init 里干了什么。
void main(void) {...buffer_init(buffer_memory_end);...
}不过我们今天只看主内存是如何管理的很简单放轻松。
进入 mem_init 函数。
#define LOW_MEM 0x100000
#define PAGING_MEMORY (15*1024*1024)
#define PAGING_PAGES (PAGING_MEMORY12)
#define MAP_NR(addr) (((addr)-LOW_MEM)12)
#define USED 100static long HIGH_MEMORY 0;
static unsigned char mem_map[PAGING_PAGES] { 0, };// start_mem 2 * 1024 * 1024
// end_mem 8 * 1024 * 1024
void mem_init(long start_mem, long end_mem)
{int i;HIGH_MEMORY end_mem;for (i0 ; iPAGING_PAGES ; i)mem_map[i] USED;i MAP_NR(start_mem);end_mem - start_mem;end_mem 12;while (end_mem--0)mem_map[i]0;
}发现也没几行而且并没有更深的方法调用看来是个好欺负的方法。
仔细一看这个方法其实折腾来折腾去就是给一个 mem_map 数组的各个位置上赋了值而且显示全部赋值为 USED 也就是 100然后对其中一部分又赋值为了 0。
赋值为 100 的部分就是 USED也就表示内存被占用如果再具体说是占用了 100 次这个之后再说。剩下赋值为 0 的部分就表示未被使用也即使用次数为零。
是不是很简单就是准备了一个表记录了哪些内存被占用了哪些内存没被占用。这就是所谓的“管理”并没有那么神乎其神。
那接下来自然有两个问题每个元素表示占用和未占用这个表示的范围是多大初始化时哪些地方是占用的哪些地方又是未占用的
还是一张图就看明白了我们仍然假设内存总共只有 8M。 可以看出初始化完成后其实就是 mem_map 这个数组的每个元素都代表一个 4K 内存是否空闲准确说是使用次数。
4K 内存通常叫做 1 页内存而这种管理方式叫分页管理就是把内存分成一页一页4K的单位去管理。
1M 以下的内存这个数组干脆没有记录这里的内存是无需管理的或者换个说法是无权管理的也就是没有权利申请和释放因为这个区域是内核代码所在的地方不能被“污染”。
1M 到 2M 这个区间是缓冲区2M 是缓冲区的末端缓冲区的开始在哪里之后再说这些地方不是主内存区域因此直接标记为 USED产生的效果就是无法再被分配了。
2M 以上的空间是主内存区域而主内存目前没有任何程序申请所以初始化时统统都是零未来等着应用程序去申请和释放这里的内存资源。
那应用程序如何申请内存呢我们本讲不展开不过我们简单展望一下看看申请内存的过程中是如何使用 mem_map 这个结构的。
在 memory.c 文件中有个函数 get_free_page()用于在主内存区中申请一页空闲内存页并返回物理内存页的起始地址。
比如我们在 fork 子进程的时候会调用 copy_process 函数来复制进程的结构信息其中有一个步骤就是要申请一页内存用于存放进程结构信息 task_struct。
int copy_process(...) {struct task_struct *p;...p (struct task_struct *) get_free_page();...
}我们看 get_free_page 的具体实现是内联汇编代码看不懂不要紧注意它里面就有 mem_map 结构的使用。
unsigned long get_free_page(void) {register unsigned long __res asm(ax);__asm__(std ; repne ; scasb\n\tjne 1f\n\tmovb $1,1(%%edi)\n\tsall $12,%%ecx\n\taddl %2,%%ecx\n\tmovl %%ecx,%%edx\n\tmovl $1024,%%ecx\n\tleal 4092(%%edx),%%edi\n\trep ; stosl\n\tmovl %%edx,%%eax\n1::a (__res):0 (0),i (LOW_MEM),c (PAGING_PAGES),D (mem_map PAGING_PAGES-1):di,cx,dx);return __res;
}就是选择 mem_map 中首个空闲页面并标记为已使用。
好了本讲就这么多只是填写了一张大表而已简单吧之后的内存申请与释放等骚操作统统是跟着张大表 mem_map 打交道而已你一定要记住它哦。
看完了闪客文章 “操作系统就用一张大表管理内存”
TODO:here