假设我们有一种数据结构foo,需要维持一个这种数据结构的双链队列,最简单,最常见的办法就是在这个数据结构的类型定义中加入两个指针;
typedef struct foo
{
struct foo *prev;
struct foo *next;
.........;
} foo;
之后的工作就是为这种数据结构写一套用于各种队列操作的子程序,由于用来维持队列的这两个指针的类型是固定(都指向foo数据结构),这些子程序不能用于其它数据结构的队列操作,换言之,需要维持多少种数据结构的队列,就得有多少套的队列操作子程序,对于使用队列较少的应用程序或许不是个问题,但是对于大量队列的内核就有点力不从心啦!
linux内核针对此问题采用了一套通用的,一般的,可以用到各种不同数据结构的队列操作.为此 linux内核代码的此项作者把指针preev和next从具体的"宿主"数据结构中
抽象出来成为一种新的数据结构list_head;这种数据结构即可以"寄宿"在具体的宿主数据结构内部,成为该数据结构的一个"连接件";也可以独立存在而成为一个队列的头
linux内核中这个数据机构位于/usr/src/kernels/2.6.32-279.el6.x86_64/include/linux/list.h中,仅仅是数据结构类型的申明,;
[list_add()>__list_Add()]
/*
* Insert a new entry between two know consecutive entries.
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static __inline__ void __list_add(
struct list_head * new,
struct list_head * prev,
struct list_head * next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
队列拖链的操作list_del():
static __inline__ void list_del(struct list_head *entry)
{
__list_del(entry->prev,entry->next);
}
调用另一个inline函数__list_del()来完成操作
static __inline__ void __list_del(struct list_head * prev,struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
page = memlist_entry(curr,struct page,list);
/**
* list_entry - get the struct for this entry
* @ptr:the &struct list_head pointer.
* @type:the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr,type,member)
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)));
小异有时候也是很重要的,稍不加重视就会造成困扰!汇编Intel,AT&T代码格式 区别;
Intel和AT&T在汇编格式上的小异:
a. Intel格式中字母大写,AT&T中小写;
b. AT&T格式中寄存器名前要加%作为前缀,Intel语句中不用;
c. 在AT&T的386 assembly中,instruct的源操作数与目标操作数的顺序与在Intel的386 assembly中正好相反,在Intel中目标在前,源在后,而在AT&T格式中则是源在前,目标在后.eg:
将寄存器exa的内容送入ebx,在Intel格式为MOVE EBA,EAX
而在AT&T格式中为"move %eax,%ebx";
d. 在AT&T格式中,access instruct Operter data size(width)由操作码名称的最后一个字母(也就是操作码的后缀)来决定,用作操作码后缀的字母有
b(express 8 bits)
w(express 16 bits)
l(express 21 bits)
在Intel格式中,则是表示内存单元的操作数前加上"BYTE PTR","WORD PTR","DWORD PTR"
eg:将FOO所指内存单元中的字节取入8bits的寄存器AL
MOV AL-操作目标, BYTE PTR(表示操作数宽带) FOO--操作源 (Intel格式)
movb FOO,%al
e. 在AT&T格式中,直接操作数要加上"$"作为前缀,而在Intel格式中则不带前缀,所以Intel格式中的"PUSH4",在AT&T格式中就变成push$4
f. 在AT&T格式中,绝对转移或调用指令jump/call的操作数(也即转移或调用的目标地址)要加上"*"作为前缀,Intel汇编指令中不带;
g. 远程的转移指令和子程序调用指令的操作码名称,在AT&T格式中为"ljmp" 和 "lcall",而在Intel格式中,则为"JMP FAR" 和”CALL FAR“;
当转移和调用的目标为直接操作数时
CALL FAR SECTION:OFFSET(Intel format)
JMP FAR SECTION:OFFSET
lcall $section,$offset
ljmp $section,$offset
与此相应的远程返回的指令
RET FAR STACK-ADJUST
lret $stack_adjust
h. 间接 indirect address general format
SECTION:[BASE+INDEX*SCALE+DISP]
section:disp(base,index,scale)
这种寻址方式常常用于在数据结构数组中访问特定元素内的一个自动,base为数组的起始地址,Scale为每个数组元素的大小,index为下标.
当需要在C语言程序中嵌入一段汇编语言程序段时,可以使用gcc提供的”asm“语句功能
#define __SLOW_DOWN_IO __ASM__ __volatile__ ("cutb %al,$0x80")
static __inline__ void atomic_add(int i,atomic_t *v)
{
__asm__ __volatile__(
LOCK "addl %1,%0"
:"=m" (v->counter)
:"ir" (i),"m" (v->counter));
}
指令部:输出部:输入部:损坏
表示约束条件的字母
m,v,o --表示内存单元 memory unit;
r --表示任何寄存器;
q --表示寄存器eax,ebx,ecx,edx;
E,F --express float;
a,b,c,d --分别表示使用寄存器eax,ebx,ecx,edx;
"S","D" --分别表示 respectively express使用寄存器esi或edi;
"I" --express constant;
看一段嵌入汇编的代码
#ifdef CONFIG_SMP
#define LOCK_PREFIX "lock ; "
#else
#define LOCK_PREFIX ""
#endif
#define ADDR (*(volatile long *) addr)
static __inline__ void set_bit(int nr,volatile void * addr)
{
__asm__ __volatile__(LOCK_PREFIX
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr);
)
}
这里的指令btsl将一个32 bits操作数中的某一位设置成l
static inline void * __memcpy(void * to,const void * from,size_t n)
{
int d0,d1,d2;
__asm__ __volatile__(
"rep";movsl
"testb $2,%b4 "
"je 1f "
"movsw "
"1: testb $1,%b4 "
"je 2f "
"movsb "
"2:"
: "=&c" (d0),"=&D" (d1),""
)
}
#if CONFIG_X86_PAE(page address entend)
#include <asm/pgtable-3level.h>
#else
#include <asm/pgtable-2level.h>
#endif
/*
*traditional i386 two-level paging structure:
*/
#define PGDIR_SHIFT 22
#define PTRS_PRE_PGD 1024
/*
*the i386 is two-level,so we don.t really have any
*PMD--page mediate directory directory physically.
*/
#define PMD_SHIFT 22
#define PTRS_PER_PMD 1
#define PTRS_PER_PTE 1-24
/*
*This handles the memory map..we could make this a config
*option,but too many people screw it up,and too few need it.
*A __PAGE_OFFSET of oxc0000000 means that the kernel has a virtual address
* space of one gigabyte,which limits the amount of physical memory you
*/
#define __PAGE_OFFSET (0xc0000000)
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
#define __pa physcially address page address(x) ((unsigned long)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
从上码子可以看出
对于系统空间而言,给定一个虚拟地址x,其物理地址是从x中减去PAGE_OFFSET;相应的
/* Re-load page tables */
asm volatile("movl %0,%%cr3"::"r" (__pa(next->pgd->page directory)));
next->pgd 即下一个进程的页面目录起始地址 start address initial address;
通过__pa()转换成物理地址(存放在某个寄存器中),然后用mov指令将其写入寄存器CR3,AT&T assembly instruct format;
每个进程的局部描述表LDT(local descributed table)都是作为一个独立的段而存在,在全局描述表GDT(global....)中要一个指向这个的起始地址
并说明该段的长度以及其他一些参数,除此,每个进程还有一个TSS(task status segement)结构,所以每个进程都要在全局描述表GDT中占据2个表项
,那么GDT容量是多少?段寄存器中用作GDT表下标的位段宽度是13 bits;
所以GDT中有可以有8192个描述项,理论上系统中最大的进程数是4090;
系统最大进程数是有LDT及GDT及其属性决定的哦;