一、关于malloc和mfree
首先介绍的是存储资源
编程中最常用的是分配和释放内存资源,这两个过程实际两类存储资源的分配和释放:
主存和盘交换区
下面展示源码部分:
#define CMAPSIZ 100
#define SMAPSIZ 100
int coremap[CMAPSIZ]; //主存区分配资源图
int swapmap[SMAPSIZ]; //交换区分配资源图
struct map //资源图中的一项的结构
{
char *m_size; //资源图项的大小
char *m_addr; //资源图项的起始地址
};
上面是内存资源分配图的细节,眨眼一看很不合理,资源图的类型是int数组,但每个资源项,即map结构体是两个char*类型,即两个int的大小。其实这是程序设计的一种捷径,由于装入程序并不检测这一点,所以可以进行这种形式的说明。所以这两个资源图中资源项个数实际为CMAPSIZ/2和SMAPSIZ/2。
下面一个例子展示资源图
一个可用区长度为15,位置在47处开始,61处结束。
一个可用区长度为13,位置在27处开始,39处结束。
一个可用区长度为7,位置在65处开始。
项 | 长度 | 地址 |
0 | 13 | 27 |
1 | 15 | 47 |
2 | 7 | 65 |
以下是malloc源码
malloc(mp, size)
struct map *mp;
{
resister int a;
resister struct map *bp;
for(bp = mp; bp->m_size; bp++) {
if(bp->m_size >= size) {
a = bp->m_addr;
bp->m_addr =+ size;
if( (bp->m_size =- size) == 0) //若当前项被分配后刚好用完全部空间
do { //则循环把之后的所有项往前移,当前项则相当于被删除
bp++;
(bp-1)->m_addr = bp->m_addr;
}while( (bp-1)->m_size = bp->m_size);
return(a); //返回分配到的其实地址
}
}
return (0);
}
第6行,逐项查找资源图
第7行,如果当前资源项的大小大于等于需要分配的内存大小,则执行分配,否则继续下一个循环。
第11行,如果刚好把资源项的内存分配完,则把后面的每一项往前移
Unix V6采用的是首次适应分配,这种方式实现简单,一旦遇到适合的资源项就马上分配,但当系统内存被分配较多时很容易出现小碎片,而且碎片不容易合并。而后来的操作系统采用一种类似内存链表这样的结构,把内存分成多种大小,然后从小到大找到适合的大小再进行分配,实现相对复杂,但是能有效减少碎片,这里就不详细说明了。
例如,如果要分配长度为5的内存,则分配后的情况变为
项 | 长度 | 地址 |
0 | 8 | 32 |
1 | 15 | 47 |
2 | 7 | 65 |
接下来是mfree函数
mfree(mp, size, aa)
struct map *mp;
{
resister struct map *bp;
resister int t;
restster int a;
a = aa;
for (bp = mp; bp->m_addr <= a && bp->m_size != 0; bp++);
if (bp > mp && (bp-1)->m_addr+(bp-1)->m_size == a){
(bp-1)->m_size =+ size;
if (a+size == bp->m_addr) {
(bp-1)->m_size =+ bp->m_size;
while (bp->m_size) {
bp++;
(bp-1)->m_addr = bp->m_addr;
(bp-1)->m_size = bp->m_size;
}
}
}
else {
if (a+size == bp->m_addr && bp->m_size) {
bp->m_addr =- size;
bp->m_size =+ size;
} else if (size) do {
t = bp->m_addr;
bp->maddr = a;
a = t;
t = bp ->m_size;
bp->m_size = size;
bp++;
}while (size = t);
}
}
第9行,注意末尾的分号,查找直至遇到第一个起始地址大于被释放地址的项
第10行,如果和前一个资源项刚好相邻,则合并之
第12行,如果和后一个资源项也相邻,也合并之,而且此时相对之前而言少了一个资源项,所以要前移后面的资源项
第22行,刚好和后一个资源项相邻的情况
第25行,不与任何资源项相邻的情况,这种情况需要增加一个资源项,把后面的资源项都后移
最后一种情况后移的代码让人看了有些混乱,实际是用了a和size作为前一个循环和后一个循环的中间变量,记录前一个循环中被覆盖资源项的地址和长度。
例如,现在在62位置释放一个长度为3的内存,则
项 | 长度 | 地址 |
0 | 8 | 32 |
1 | 25 | 47 |
二、关于print等函数
这里的printf等函数不是C库中的函数,是运行于内核态的函数,不具有缓冲区。而我们平时编程常用的printf是运行于用户态的,默认带行缓冲的。
printf(fmt,x1,x2,x3,x4,x5,x6,x7,x8,x9,xa,xb,xc)
char fmt[];
{
resister char *s;
resister *adx, c;
adx = &x1;
loop:
while( (c = *fmt++) != '%') {
if(c == '\0')
return;
putchar(c);
}
c = *fmt++;
if(c == 'd' || c == 'l' || c == 'o')
printn(*adx, c == 'o' ? 8 : 10);
if(c == 's') {
s = *adx;
while(c = *s++)
putchar(c);
}
adx++;
goto loop;
}
第7行,让adx指向第一个参数
第9行,while循环中,在遇到‘%’之前的所有字符全部输出,而遇到‘\0'则从函数中返回第15行,检查'%'字符后面跟着的字符,printn是一个处理整型数值输出的函数
第17行,若为字符串的输出,则逐个字符输出参数中字符串的内容
第22行,把adx指向下一个参数,然后从第9行开始重复。
进程的栈底在进程内存空间的高位,从高位往低位生长。而函数调用时,参数是逆序进栈的,所以adx++指向下一个参数的内存空间
printn源代码
printn(n, b)
{
resister a;
if(a = ldiv(n, b) )
printn(a, b);
putchar(lrem(n, b) + '0');
}
这个函数通过递归,从后往前把余数输出,和我们平时算十进制与二进制转换的方法一样
putchar源码
putchar(c)
{
resister rc, s;
rc = c;
if(SW->intes == 0)
return;
while( (KL->xsr&0200) == 0)
;
if(rc == 0)
return;
s = KL->xsr;
KL->xsr = 0;
KL->xbr = rc;
if(rc == '\n') {
putchar('\r');
putchar(0177);
putchar(0177);
}
putchar(0);
KL->xsr = s;
}
第8行,KL存放发送器状态寄存器的地址,当第7位不为0时,一直处于忙等待。
这一部分设计硬件知识,需要查看相关手册。本人对这方面也不甚了解........