Welcome 微信登录

首页 / 操作系统 / Linux / 深入浅出Linux之内核基础层

前言将内核分成了内核基础层和内核应用层。记得我们的目标是“快速流畅的阅读内核代码”,为了达到这个目标,必须先了解内核的基础和数据结构。这些知识使用范围很广,如果不了解,在内核代码的理解上就容易出现障碍。  1.内核对内存的使用  简单说,内核提供了两个层次的内存分配接口。一个是从伙伴系统分配,一个是从slab分配。关于伙伴系统和slab机制,在后面的章节再分析。这里只需要了解,伙伴系统是最底层的内存管理机制,提供基于基于页式的内存管理,而slab是伙伴系统之上的内存管理,提供基于对象的内存管理。   从伙伴系统分配内存的调用是alloc_pages,注意此时得到的页面的地址,如果要获得能使用的内存地址,还需要page_address调用来获得内存地址。   如果要直接获得内存地址,需要使用__get_free_pages。__get_free_pages其实封装了alloc_pages和page_address两个函数。   alloc_pages申请的内存是以页为单元的,最少也要一个页。如果只是一小块内存,一个页就浪费了,而且内核中很多应用也希望一种对象化的内存管理,希望内存管理能自动的构造和析构对象,这都很接近面向对象的思路了。这就是slab内存管理。   要从slab申请内存,则需要创建一个slab对象,使用kmem_cache_create创建slab对象,kmem_cache_create可以提供对象的名字和大小,构造函数和析构函数等。然后通过kmem_cache_alloc和kmem_cache_free来申请和释放内存。   内核中常用的kmalloc其实也是slab管理。只不过内核已经创建好了一些固定大小的对象,用户通过kmalloc申请的时候,就使用了这些对象。 提供一个内核的例子:======================================================================创建slab对象:bh_cachep = kmem_cache_create("buffer_head",                               sizeof(struct buffer_head), 0,                               (SLAB_RECLAIM_ACCOUNT|SLAB_PANIC|                               SLAB_MEM_SPREAD),                               init_buffer_head,                               NULL);  创建了一个名为“buffer_head"的对象,对象的初始化函数为init_buffer_head 申请slab对象:struct buffer_head *ret = kmem_cache_alloc(bh_cachep, gfp_flags); 释放slab对象:kmem_cache_free(bh_cachep, bh);   内核中还有一个内存分配调用:vmalloc。Vmalloc涉及到高端内存和建立页表映射的概念,作为内核基础的本节就不分析了。  理解了上面的几个函数调用,阅读内核代码的时候,就可以清晰内核中对内存的使用。至于内存管理的结构和细节,在后面我们再讨论。 2.内核使用的进程调度  内核中经常需要使用进程的调度。首先看一个例子。#define wait_event(wq, condition)  do {          if (condition)                      break;          __wait_event(wq, condition); } while (0) #define __wait_event(wq, condition)  do {          DEFINE_WAIT(__wait);                                                          for (;;) {                prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);                if (condition)                           break;                    schedule();          }          finish_wait(&wq, &__wait); } while (0)  上文定义了一个wait对象,然后设置进程睡眠。如果有其它进程唤醒这个进程后,判断条件是否满足,如果满足则删除wait对象,否则进程继续睡眠。  这是一个很常见的例子,使用wait_event调用实现进程调度的实例在内核中很多,而且内核中还实现了一系列的函数,简单介绍一下。
  • wait_event_timeout :和wait_event的区别是有时间限制,如果条件满足,进程恢复运行,或者时间到达,进程同样恢复运行
  • wait_event_interruptible:和wait_event类似,不同之处进程处于可打断的睡眠。而wait_event设置进程处于不可打断的睡眠。两者区别何在?可打断的睡眠进程可以接收到信号,而不可打断的睡眠进程不能处理信号。
  • wait_event_interruptible_timeout:和wait_event_interruptible相比,多个了时间限制。在规定的时间到达后,进程恢复运行。
  • wait_event_interruptible_exclusive:和wait_event_interruptible区别是排它性的等待。何谓排它性的等待?有一些进程都在等待队列中,当唤醒的时候,内核是唤醒所有的进程。如果进程设置了排它性等待的标志,那么唤醒所有非排它性的进程和一个排它性进程。 
3.内核的软中断和tasklet  linux内核把中断的执行分拆成两部分。和硬件关系紧密,必须关中断执行的操作放在中断上下文中执行,而可以开中断执行的操作则放在软中断上下文执行。   为此目的,linux内核定义了几个缺省的软中断,网络设备有自己的发送和接收软中断,块设备也有自己的软中断。为了方便使用,内核还定义了一个TASKLET软中断。TASKLET是一种特殊的软中断,一个TASKLET只能由一个CPU 执行,同一刻,不同的TASKLET可以在不同的CPU上执行,而同样的TASKLET只能有一个在执行。这个和软中断不同,软中断同一刻可以在不同的CPU并行执行,因此软中断必须考虑重入的问题。  内核中很多地方使用了tasklet。先分析一个例子:======================================================================DECLARE_TASKLET_DISABLED(hil_mlcs_tasklet, hil_mlcs_process, 0);tasklet_schedule(&hil_mlcs_tasklet);  上面的例子首先定义了一个tasklet,它的执行函数是hil_mlcs_process。当程序中调用tasklet_schedule,会把要执行的结构插入到一个tasklet链表。然后触发一个TASKLET软中断。每个CPU都有自己的tasklet链表,内核会根据情况,确定在何时执行tasklet。   可以看到,TASKLET使用起来很简单。本节只需要了解在内核如何使用即可。 4.工作队列  工作队列和tasklet相似,都是一种延缓执行的机制。不同之处是工作队列有自己的进程上下文,所以工作队列可以睡眠,可以被调度。而tasklet一般要在软中断上下文中执行。看一个工作队列的例子:======================================================================        INIT_WORK(&ioc->sas_persist_task,           mptsas_persist_clear_table,           (void *)ioc);           schedule_work(&ioc->sas_persist_task);    使用工作队列很简单,schedule_work就把用户定义的work_struct加入系统的队列中,并唤醒系统线程去执行。那么是那一个系统线程执行用户的work_struct?实际上,内核初始化的时候,就要创建一个工作队列keventd_wq,同时为这个工作队列创建系统线程(缺省是为每个CPU创建一个系统线程)。   内核同时还提供了create_workqueue和create_singlethread_workqueue函数,这样用户可以创建自己的工作队列和执行线程,而不用内核提供的工作队列。看内核的例子:======================================================================kblockd_workqueue = create_workqueue("kblockd");int kblockd_schedule_work(struct work_struct *work){
return queue_work(kblockd_workqueue, work);
}  kblockd_workqueue是内核通用块层提供的工作队列,需要由kblockd_workqueue执行的工作,就要调用kblockd_schedule_work,其实就是调用queue_work把work加入到kblockd_workqueued工作队列的任务链表。   create_singlethread_workqueue和create_workqueue类似,不同之处像名字揭示的一样,create_singlethread_workqueue只创建一个内核线程,而不是为每个CPU创建一个内核线程。深入浅出Linux之内核数据结构深入浅出Linux之前言相关资讯      Linux内核 
  • IT人员必须了解的六项Linux内核变  (今 12:05)
  • Linux 内核更新:3.10.98、3.14.62  (02月26日)
  • Linux:让手机运行主线内核  (11/26/2015 22:16:17)
  • Linux内核自防护项目  (05月24日)
  • Linux 内核架构的理解  (12/09/2015 09:01:01)
  • Linux内核被指缺乏安全性  (11/07/2015 08:28:47)
本文评论 查看全部评论 (0)
表情: 姓名: 字数