存档在 2012年5月

内核任务

2012年5月31日

内核任务是指内核中执行的一切活动对象,每个内核任务都拥有一个独立的程序计数器、栈和一组寄存器。我们可以将Linux内核看作是不断对请求进行响应的服务器,这里的请求可能来自CPU上正在执行的进程,也可能是来自发出中断请求的外部设备。这个类比用来强调内核中各个任务之间并不是严格按照顺序执行的,而是采用交错执行的方式。本文简单说明内核中的任务分类,以及每种内核任务的特点。

内核线程

内核线程只运行在内核空间,它为内核完成一些周期性任务,比如用于执行工作队列的keventd线程,用于执行内存回收的kswapd线程以及用于将脏缓冲区中的内容刷新到磁盘上的pdflush线程等。内核线程与用户进程在某些方面比较类似,比如它们被内核中同一个进程调度器所调度、都通过do_fork()创建等。这主要是因为内核线程与进程都通过进程描述符task_struct来描述,我们可以将内核线程看作是运行在内核空间的进程。内核中使用thread来描述内核线程是因为它并不拥有用户空间,因此内核线程之间的切换是迅速的。虽然都使用了线程一词,但是应该和用户态的线程有所区分。

系统调用

用户态进程必须通过系统调用才能进入内核并获得内核提供的服务,比如访问硬件设备。系统调用是一种异常,它通过128号中断向量向内核发出一个明确的请求。系统一旦执行某个系统调用,就从用户态切换到内核态,接下来内核就代表发出系统调用的进程执行,执行完毕后又返回用户空间。由于系统调用是内核代表进程执行,因此它可以获得当前进程的信息,比如可以访问当前进程的描述符中的信息,而且系统调用的执行使用的是当前发出系统调用进程的时间片。

中断处理程序

根据Intel的说法,异步中断也被简称为中断,它是由硬件设备依照CPU时钟信号随机产生的。这类中断可以打断任何正在执行的内核任务。在响应一个特定中断的时候,内核会执行一个函数,该函数称为中断处理程序(Interrupt Handler)。如果一条中断线上共享了多个设备,那么每个设备将对应一个中断服务例程(Interrupt Server Routine,ISR)。当该某个中断发生时,内核会调用相应的中断处理程序。该中断处理程序首先在内核态堆栈中保存IRQ和寄存器的值,然后响应该中断,接着执行共享这个中断线上所有设备的中断服务例程,执行完毕后恢复之前被打断的内核任务执行现场。

异常处理程序

同步中断也被称为异常,它是由CPU控制单元产生的,只有在一条指令终止执行后CPU才会发出异常。内核为每一种异常提供了一个专门的异常处理程序。异常处理程序的执行过程与中断处理程序类似,它首先将大多数寄存器的值压入内核堆栈中,接着调用响应异常处理程序,最后从异常处理程序中返回并向产生异常的进程发出一个信号。

可延迟函数

中断随时可能发生,因此中断处理程序也就随时会被执行。为了能尽快恢复被中断的代码再次执行,中断处理程序必须快速运行完毕。但是,中断处理程序极有可能和硬件设备进行交互,它需要花一些时间和硬件进行数据交互,比如等待数据的到来,从外设拷贝数据到内存等。中断程序既要快速运行,又要完成大量工作,这两者显然存在矛盾。为了解决这个问题,内核将中断处理的过程分为上下两部分,上部分即中断处理程序,主要对中断请求进行快速响应;下半部分主要完成中断处理过程中对时间要求相对宽松的工作。下半部分包含三种机制:软中断(softirq,与上文中软件中断不同),tasklet以及工作队列。鉴于工作队列通过keventd内核线程来执行,因此将软中断和tasklet统称为可延迟函数。

Linux内存管理中的slab分配器

2012年5月18日

Linux内核中基于伙伴算法实现的分区页框分配器适合大块内存的请求,它所分配的内存区是以页框为基本单位的。对于内核中小块连续内存的请求,比如说几个字节或者几百个字节,如果依然分配一个页框来来满足该请求,那么这很明显就是一种浪费,即产生内部碎片(internal fragmentation)

为了解决小块内存的分配,Linux内核基于Solaris 2.4中的slab分配算法实现了自己的slab分配器。除此之外,slab分配器另一个主要功能是作为一个高速缓存,它用来存储内核中那些经常分配并释放的对象。

1.slab分配器的基本原理

slab分配器中用到了对象这个概念,所谓对象就是内核中的数据结构以及对该数据结构进行创建和撤销的操作。它的基本思想是将内核中经常使用的对象放到高速缓存中,并且由系统保持为初始的可利用状态。比如进程描述符,内核中会频繁对此数据进行申请和释放。当一个新进程创建时,内核会直接从slab分配器的高速缓存中获取一个已经初始化了的对象;当进程结束时,该结构所占的页框并不被释放,而是重新返回slab分配器中。如果没有基于对象的slab分配器,内核将花费更多的时间去分配、初始化以及释放一个对象。

slab分配器有以下三个基本目标:

1.减少伙伴算法在分配小块连续内存时所产生的内部碎片;

2.将频繁使用的对象缓存起来,减少分配、初始化和释放对象的时间开销。

3.通过着色技术调整对象以更好的使用硬件高速缓存;

2.slab分配器的结构

slab分配器为每种对象分配一个高速缓存,这个缓存可以看做是同类型对象的一种储备。每个高速缓存所占的内存区又被划分多个slab,每个slab是由一个或多个连续的页框组成。每个页框中包含若干个对象,既有已经分配的对象,也包含空闲的对象。slab分配器的大致组成图如下:

每个高速缓存通过kmem_cache结构来描述,这个结构中包含了对当前高速缓存各种属性信息的描述。所有的高速缓存通过双链表组织在一起,形成高速缓存链表cache_chain。每个kmem_cache结构中并不包含对具体slab的描述,而是通过kmem_list3结构组织各个slab。该结构的定义如下:

struct kmem_list3 {
        struct list_head slabs_partial;
        struct list_head slabs_full;
        struct list_head slabs_free;
        unsigned long free_objects;
        unsigned int free_limit;
        unsigned int colour_next;
        spinlock_t list_lock;
        struct array_cache *shared;
        struct array_cache **alien;
        unsigned long next_reap;
        int free_touched;
};

可以看到,该结构将当前缓存中的所有slab分为三个集合:空闲对象的slab链表slabs_free,非空闲对象的slab链表slabs_full以及部分空闲对象的slab链表slabs_partial。每个slab有相应的slab描述符,即slab结构,它的定义如下:

struct slab {
        struct list_head list;
        unsigned long colouroff;
        void *s_mem;
        unsigned int inuse;
        kmem_bufctl_t free;
        unsigned short nodeid;
};

slab描述符中的list字段标明了当前slab处于三个slab链表的其中一个。我们将上述的slab分配器进行细化,可以得到下面的结构图:

3.高速缓存的分类

slab高速缓存分为两大类,普通高速缓存和专用高速缓存。普通高速缓存并不针对内核中特定的对象,它首先会为kmem_cache结构本身提供高速缓存,这类缓存保存在cache_cache变量中,该变量即代表的是cache_chain链表中的第一个元素;另一方面,它为内核提供了一种通用高速缓存。专用高速缓存是根据内核所需,通过指定具体的对象而创建。

3.1 普通高速缓存

slab分配器中kmem_cache是用来描述高速缓存的结构,因此它本身也需要slab分配器对其进行高速缓存。cache_cache变量保存着对高速缓存描述符的高速缓存。

static struct kmem_cache cache_cache = {
        .batchcount = 1,
        .limit = BOOT_CPUCACHE_ENTRIES,
        .shared = 1,
        .buffer_size = sizeof(struct kmem_cache),
        .name = "kmem_cache",
};

slab分配器所提供的小块连续内存的分配是通过通用高速缓存实现的。通用高速缓存所提供的对象具有几何分布的大小,范围为32到131072字节。内核中提供了kmalloc()和kfree()两个接口分别进行内存的申请和释放。

3.2 专用高速缓存

内核为专用高速缓存的申请和释放提供了一套完整的接口,根据所传入的参数为具体的对象分配slab缓存。

高速缓存的申请和释放

kmem_cache_create()用于对一个指定的对象创建高速缓存。它从cache_cache普通高速缓存中为新的专有缓存分配一个高速缓存描述符,并把这个描述符插入到高速缓存描述符形成的cache_chain链表中。kmem_cache_destory()用于撤销一个高速缓存,并将它从cache_chain链表上删除。

slab的申请和释放

kmem_cache_alloc()在其参数所指定的高速缓存中分配一个slab。相反,kmem_cache_free()在其参数所指定的高速缓存中释放一个slab。

Linux内存管理实践-使用fault()实现内存映射

2012年5月15日

内核态与用户态进行数据交互通常是这样一种模型:内核利用自身的特权通过特定的服务程序采集、接收和处理数据;接着,用户态程序和内核服务程序进行数据交互,或接收内核态的数据,或向内核态写入数据。通过传统的那些对文件操作的系统调用就可以完成这样的工作,但是我们有时候需要通过访问用户空间的内存来直接读取内核数据,因为这样可以免去数据在内核态与用户态之间拷贝所花费的时间。

本文基于以上背景,以Linux字符设备驱动为基础,通过内存映射将内核中的一部分虚拟内存直接映射到用户空间,使得用户在访问内存时等同于直接访问内核空间,从而直接获取内核空间的数据。

1.实现原理

不管进程是在用户空间访问数据还是在内核空间访问数据,它所面临的都是虚拟地址。由于Linux对分段机制进行了特殊处理,因此这里的虚拟地址就等同于线性地址。按照一开始我们提出的要求,进程通过访问用户虚拟地址A来达到直接访问内核虚拟地址B中所存储数据的目的,这里的地址A和B必然不相同。那么,如何通过不同的虚拟地址来访问相同的数据?我们可以将虚拟地址A和B都映射到同一块物理内存,就可以实现内核空间和用户空间之间的数据共享。

示意图如下:

虚拟内存和物理内存之间如何联系?当然是通过页表了。我们在内核空间提前分配好缓冲区,并且向该缓冲区写入数据,此时内核会自动将该缓冲区对应的内核虚拟地址与实际的某一快物理内存进行关联,并将它们的映射关系保存在内核页表中。当在内核空间分配内存时时,上述工作自动被完成,比如通过kmalloc()分配内存时。

一旦在内核空间中分配了内存,随之就确定了物理内存。现在我们需要做的是将用户虚拟地址与物理内存进行关联,也就是说我们要将这个映射关系写入进程的用户页表。整个关联的过程是内核缺页异常处理程序完成的,这个处理过程比较复杂。我们要在内核中实现的并不是缺页异常处理程序,因为内核已经实现的很完美,而只需完成其中的一小部门。具体如何实现下问会详细说明。

2.用户态程序的实现

在本文所描述的内存管理试验,用户态程序首先通过open()打开字符设备文件mapdrv,该系统调用执行成功时返回文件描述符;通过mmap()将该设备文件映射到当前进程的用户空间中,该系统调用执行成功时返回指向映射区域的指针;最后通过该指针打印数据。用户态程序的实现如下所示:

#define LEN (10 * 4096)

int main(void)
{
	int fd, ret = 0;
	char *vadr;
	int i;

	if ((fd = open("/dev/mapdrv_k", O_RDWR)) < 0) {
		perror("open");
		ret = -1;
		goto fail;
	}
	vadr = mmap(0, LEN, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, 0);

	if (vadr == MAP_FAILED) {
		perror("mmap");
		ret = -1;
		goto fail_close;
	}

	printf("%s\n", vadr);

	if (-1 == munmap(vadr, LEN))
		ret = -1;
fail_close:
	close(fd);
fail:
	exit(ret);
}

用户态程序的实现并不复杂,因为它的主要作用是对内核模块程序的测试。由于用户态程序是对特定的字符设备文件mapdrv进行操作,所以程序中所使用的系统调用将会调用file_operations结构中对应的钩子函数。比如mmap系统调用在执行时会调用mapdrv设备驱动中的mmap钩子函数,虽然两者同名,但是mmap钩子函数所实现的功能只是mmap系统调用执行过程中的一部门,该钩子函数是file_operations结构中的成员。两者的函数原型如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int (*mmap) (struct file *, struct vm_area_struct *);

如何实例化open和mmap等钩子函数便是整个内核模块程序实现的关键。

3.字符设备驱动程序的实现

整个内核模块程序是以字符设备驱动为基础进行实现的。该程序模块加载函数与一般字符设备驱动程序完成的工作一致:

1.申请设备号;

2.为描述字符设备的数据结构分配空间,并进行初始化;

3.将该字符设备注册到内核中;

在本文所描述的实验中,模块加载函数除了完成上述功能,还要完成以下功能:

	kmalloc_area = kmalloc(MAPLEN, GFP_KERNEL);
	if (!kmalloc_area)
		goto fail4;

	for (page = virt_to_page(kmalloc_area);
			page < virt_to_page(kmalloc_area + MAPLEN); page++) {
		SetPageReserved(page);
	}

首先,通过kmalloc()分配一块内存用于在内核空间保存数据;通过SetPageReserved()将缓存数据的页面常驻内存,防止被换出到磁盘;将一段字符串拷贝到这片内存中。完成初始化函数后,字符设备驱动中最重要的就是实现file_operations结构中的钩子函数。在本文所述的实验中,我们只需实现三个钩子函数。

static struct file_operations mapdrv_fops = {
	.owner = THIS_MODULE,
	.mmap = mapdrv_mmap,
	.open = mapdrv_open,
	.release = mapdrv_release,
};

int mapdrv_open(struct inode *inode, struct file *file)
{
	struct mapdrv *md;

	printk("device is opened..\n");
	md = container_of(inode->i_cdev, struct mapdrv, mapdev);
	atomic_inc(&md->usage);
	return 0;
}

int mapdrv_release(struct inode *inode, struct file *file)
{
        struct mapdrv* md;

        printk("device is closed..\n");
        md = container_of(inode->i_cdev, struct mapdrv, mapdev);
        atomic_dec(&md->usage);
        return 0;
}

可以看到,open和release钩子函数的实现十分简单,只是打印相应语句以及更新设备的引用计数。事实上,我们不实现这两个钩子函数对整个驱动程序也没有任何影响。因为他们分别在open和close系统调用执行的过程中被调用,而这两个系统调用已经完成了打开文件和关闭文件的所有工作。因此,open和release钩子函数只是打印一些日志信息,方便用户查看。因此,同名的钩子函数和系统调用并不是等价的关系。

4.通过fault()实现内存映射

mmap系统调用是将本地文件映射到进程的用户空间,如果执行成功,进程的地址空间中会新增一块虚拟内存区域(但并不是每次调用mmap()都会增加一个vma,因为可能会出现内存区域之间的合并)

mmap钩子函数的回调只是整个系统调用执行过程中的一部分,这个钩子函数完成的主要功能是将新增的vma中的操作集进行实例化。具体实现代码如下:

int mapdrv_mmap(struct file *file, struct vm_area_struct *vma)
{
	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;

	unsigned long size = vma->vm_end - vma->vm_start;
	if (offset & ~PAGE_MASK) {
		printk("offset not aligned: %ld\n", offset);
		return -ENXIO;
	}
	if (size > MAPLEN) {
		printk("size too big\n");
		return -ENXIO;
	}

	if ((vma->vm_flags & VM_WRITE) && !(vma->vm_flags & VM_SHARED)) {
		printk("writeable mappings must be shared, rejecting\n");
		return -EINVAL;
	}

	vma->vm_flags |= VM_LOCKED;
	if (offset == 0) {
		vma->vm_ops = &map_vm_ops;
		map_vopen(vma);
	} else {
		printk("offset out of range\n");
		return -ENXIO;
	}
	return 0;
}

首先,offset中保存映射的首页在文件中的偏移量,该偏移量必须是页大小的整数倍,否则将不能进行映射。这一点在实现上将offset与PAGE_MASK宏进行位运算即可判断。接着,判断映射区域的长度是否超出了本实验中预设的长度大小。

如果上述两个条件都合法,那么接下来将进行最为重要的操作,将vma中的操作集进行实例化。vma的操作集就是专门对所属vma进行操作的钩子函数集合,内核中通过vm_operations_struct结构对其描述:

struct vm_operations_struct {
        void (*open)(struct vm_area_struct * area);
        void (*close)(struct vm_area_struct * area);
        int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);
        ……
}

参考上述代码,具体的实例化操作就是定义该类型的变量map_vm_ops,实现所需钩子函数,并将该变量与vm中的操作集字段进行挂接。

我们这里用到的主要有以下三个钩子函数:

open:当指定的vma加入到一个地址空间时,该函数被调用。

close:当指定的vma从地址空间删除时,该函数被调用。

fault:当要访问的页不再物理内存时,该函数被缺页处理程序调用。

这三个钩子函数的实现代码如下:

static struct vm_operations_struct map_vm_ops = {
	.open = map_vopen,
	.close = map_vclose,
	.fault = map_fault,
};

void map_vopen(struct vm_area_struct *vma)
{
	printk("mapping vma is opened..\n");
}

void map_vclose(struct vm_area_struct *vma)
{
	printk("mapping vma is closed..\n");
}

可以看到,vma的open和close两个钩子函数没有做什么具体工作,因为打开和关闭一个vma的工作全部由内核负责,但是在mmap钩子函数中我们必须显示的调用map_open()。

这里我们重点说明falut钩子函数的实现。当用户要访问vma中的页,而该页又不在内存时,将发生缺页异常,fault钩子函数会在整个缺页处理程序中被调用。整个过程大致如下:

1.找到缺页地址所在的vma。

2.如果有必要分配各级页表项。

3.如果页表项对应的物理页面不存在,则回调当前vma中的fault钩子函数,它返回物理页面描述符。

4.将物理页面地址填充到相应页表项中。

5.完毕。

可以看到,fault钩子函数所实现的主要功能就是返回所需的物理内存描述符。

根据本文第一部分所描述实现原理,我们通过kmalloc()分配一块虚拟内存,可以通过virt_to_page()获得该虚拟内存对应的物理页框描述符,最后将该物理页框描述符返回到缺页异常处理程序中。至于用户页表的更新,那是缺页异常处理程序负责的事情,我们不必理会。

int map_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
	struct page *page = virt_to_page(kmalloc_area);

	get_page(page);
	vmf->page = page;
	printk("the requiring page is returned..\n");

	return 0;
}

通过上述实现过程,我们就将用户虚拟地址A和内核虚拟地址B映射到了同一的物理内存上,从而实现进程访问用户地址时直接获得内核数据的功能。

本实验涉及的知识点比较多,比如字符设备驱动程序的基本模型,Linux内存管理等相关知识。感兴趣的童鞋可以参考:

1.内核之旅网站,http://www.kerneltravel.net/journal/v/mem.htm

2.Linux设备驱动程序,第十五章

 

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key