Unix V6内核源码的理解(一)

一、关于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');
}

ldiv为长整数相除,lrem为长整数求余。这两个函数由汇编实现

这个函数通过递归,从后往前把余数输出,和我们平时算十进制与二进制转换的方法一样



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;
}

第6行,SW为只读处理器寄存器的地址,存放这控制台开关寄存器的设定值,而SW所指向的是一个结构体,该结构体只有一个成员,即int型的intes

第8行,KL存放发送器状态寄存器的地址,当第7位不为0时,一直处于忙等待。

这一部分设计硬件知识,需要查看相关手册。本人对这方面也不甚了解........

你可能感兴趣的:(Unix/Linux,unix,struct,c,编程,存储,汇编)