代码改变世界

linux源码分析(五)-start_kernel

2016-10-28 16:45  轩脉刃  阅读(3072)  评论(4编辑  收藏  举报

前置:这里使用的linux版本是4.8,x86体系。

local_irq_disable();

这个函数是做了关闭中断操作。和后面的local_irq_enable相对应。说明启动的下面函数是不允许被中断抢占的。这个函数追下去会发现下面的代码:

static inline void native_irq_disable(void)
{
    asm volatile("cli": : :"memory");
}

这个写法是linux的内联汇编写法。在C语言中写汇编语言。实际上调用的是汇编cli命令。cli命令是禁用中断功能。http://rock3.info/blog/2013/11/24/linux-c中调用汇编用法/

接着start_kernel,linux关闭完中断之后,还使用了一个变量early_boot_irqs_disabled来标记已经关闭irq了。

这里稍微说说irq的概念,我们把中断分为两个概念,一个是上半部,一个是下半部,上半部指的是硬件直接要求立即响应的中断。下半部指的是可以在某个特定时间之后执行的。这里的IRQ就是一个上半部概念。每个硬件设备都有一个irq线,通过这个线把中断描述符传递给CPU,CPU获取中断之后立即执行对应已经注册的操作。

boot_cpu_init()

这个函数功能是初始化第一个CPU。

void __init boot_cpu_init(void)
{
    int cpu = smp_processor_id();

    /* Mark the boot cpu "present", "online" etc for SMP and UP case */
    set_cpu_online(cpu, true);
    set_cpu_active(cpu, true);
    set_cpu_present(cpu, true);
    set_cpu_possible(cpu, true);
}

先获取cpu的id,在smp下,获取第一个处理器ip,非smp,第一个cpu的id为0。后面就是设置cpu的四个标志位。

page_address_init()

这个是页地址初始化操作。

我们先要了解下段式管理和页式管理。我们会有三个地址,逻辑地址,虚拟地址,物理地址。CPU要将一个逻辑地址转换为物理地址,需要两步:首先CPU利用段式内存管理单元,先将逻辑地址转换为线性地址(虚拟地址)。再利用页式管理单元,把虚拟地址,转化为物理地址。

形象理解,段式管理就是一个大大的内存按照目的分为几段,有的段比较大,有的段比较小。但是呢,每个段的最低地址位都是0,实际的地址位是段的偏移量。所以,这里就存在逻辑地址和虚拟地址的转换了。为什么要进行分段管理呢?进行分段管理,能使得我们有可能对不同的内存段赋予不同的权限管理。

页式管理是一段内存,按照指定大小划分,每4k为一页。这里就有一个虚拟地址和物理地址的映射关系了。比如在一个三级的页式管理中,一个虚拟地址32位按照10,10,12分为3段,前10位是页目录地址,后10位是页表地址,最后12位是偏移量地址。这里其实有个奇怪的地方了。

为什么要进行页式管理呢?不管是否有分页管理,一个32位的地址,最多指向的也就是4G内存空间。页式管理其实是为了更好地利用内存。比如假设内存是连续分配的,进程A获取了1~100的内存空间,进程B获取了100~104的内存空间,进程C获取了104~200的内存空间。现在进程B释放空间了,但是只有很小的4。这个时候,如果后续的进程要申请的空间都是大于4的,那么100~104这个内存空间段就永远没有办法被分配。而使用页式管理就有办法避免这个问题。它可以让程序使用的内存在逻辑上是连续的,在物理上是离散的。

回到linux中,由于仅有一部分体系支持段式管理,基于兼容的原因吧,linux并没有支持段式管理。换句话说,linux把内存块当作是一个段。所以实际上,在linux中,逻辑地址和虚拟地址是一样的。但是linux把页式管理是全盘接受了。

回到这个函数,page_address_init()

查找这个函数的定义,你可以看到根据宏不同有两种定义,一种是

#if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL)
#define page_address(page) lowmem_page_address(page)
#define set_page_address(page, address)  do { } while(0)
#define page_address_init()  do { } while(0)
#endif

一种是

void __init page_address_init(void)
{
    int i;

    for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
        INIT_LIST_HEAD(&page_address_htable[i].lh);
        spin_lock_init(&page_address_htable[i].lock);
    }
}

#endif    /* defined(CONFIG_HIGHMEM) && !defined(WANT_PAGE_VIRTUAL) */

首先,这里分配的页地址空间指的是内核的页地址空间。linux把4G的虚拟地址空间分为3G用户地址空间+1G内核地址空间。基本上是使用物理地址+3G的方式进行直接映射的。所以一般初始化内核地址空间的时候并不需要做任何操作。但是,当有些系统自身需要的内存大于896M的时候,就出现问题了。首先1G的内核空间,linux内核会把它分为896M的操作系统使用的空间,和128M的IO映射空间。当有些操作系统自身需要的内存大于896M,那么就难免有一些操作系统空间需要使用虚拟地址的形式,实际上是占用了原本用户的系统空间,这一部分空间就叫做high memory。(那么想对,原本已经有的896M的空间叫做low memory)。这一部分的空间映射,是保存在128M的IO映射空间里面的。http://blog.sina.com.cn/s/blog_6488248f0100wu6v.html