linux3.10 系统start_kernel初始化流程详解(二)

(20)jump_label_init(); 这边是个空函数,不做处理
(21)setup_log_buf(0);

这个函数如果初始化了new_log_buf_len变量,则为log buf 分配空间,可以看到他的定义方式:

early_param("log_buf_len", log_buf_len_setup);因为在bootargs中没有带入log_buf_len参数,所以并没有初始化,这里不做处理

(22)pidhash_init();

初始化用来存储进程pid的hash表

void __init pidhash_init(void)
{
	unsigned int i, pidhash_size;

	pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
					   HASH_EARLY | HASH_SMALL,
					   &pidhash_shift, NULL,
					   0, 4096);   为hash表分配空间
	pidhash_size = 1U << pidhash_shift;

	for (i = 0; i < pidhash_size; i++)
		INIT_HLIST_HEAD(&pid_hash[i]);  初始化hash表头
}
(23)vfs_caches_init_early();

初始化dnode和dentry的hash列表,这个估计是用来缓存 dnode和dentry结构的,和文件系统有关

(24)sort_main_extable();  这边没有调用
(25)trap_init();空函数
(26)mm_init();   mm初始化函数
static void __init mm_init(void)
{
	/*
	 * page_cgroup requires contiguous pages,
	 * bigger than MAX_ORDER unless SPARSEMEM.
	 */
	page_cgroup_init_flatmem();   空函数没有使用
	mem_init();   伙伴系统的初始化,把bootmem释放到伙伴系统中,启动伙伴系统
	kmem_cache_init(); 初始化slab系统
	percpu_init_late();把percpu相关的全局变量用slab重新分配空间并替代
	pgtable_cache_init(); 空函数
	vmalloc_init(); vm area的初始化,这个函数初始化完以后可以用vmalloc分配虚拟地址连续,但物
理地址可能不连续的空间
}
(26.1)mem_init();

mem_init函数把内存从bootmem释放到伙伴系统中,参考如下链接:

https://blog.csdn.net/oqqYuJi12345678/article/details/97618343

(26.2)kmem_cache_init();

初始化slab系统,slab系统初始化完毕以后,在linux内核中,小于1页的空间,一般都会调用kmalloc来进行分配,kmalloc

利用的就是slab机制,slab一般都是从一个物理页中分配出多份空间,来进行再分配,所以kmalloc分配的内存在物理上都是连续的。具体初始化过程,写在另一篇博文里:

https://blog.csdn.net/oqqYuJi12345678/article/details/97691622

(26.3)percpu_init_late();
void __init percpu_init_late(void)
{
	struct pcpu_chunk *target_chunks[] =
		{ pcpu_first_chunk, pcpu_reserved_chunk, NULL };
之前setup_per_cpu_areas的时候只初始化了pcpu_first_chunk
	struct pcpu_chunk *chunk;
	unsigned long flags;
	int i;
    由于前面初始化schunk的时候,slab系统还没有初始化好,使用了静态的map,这边向slab系统
申请内存,把该静态的map用动态分配的空间替换掉
	for (i = 0; (chunk = target_chunks[i]); i++) {
		int *map;
                map大小为128个
		const size_t size = PERCPU_DYNAMIC_EARLY_SLOTS * sizeof(map[0]);

		BUILD_BUG_ON(size > PAGE_SIZE);

		map = pcpu_mem_zalloc(size);
		BUG_ON(!map);

		spin_lock_irqsave(&pcpu_lock, flags);
		memcpy(map, chunk->map, size);
		chunk->map = map;
		spin_unlock_irqrestore(&pcpu_lock, flags);
	}
}

 

(26.4)vmalloc_init();

 

void __init vmalloc_init(void)
{
	struct vmap_area *va;
	struct vm_struct *tmp;
	int i;

	for_each_possible_cpu(i) {
		struct vmap_block_queue *vbq;
		struct vfree_deferred *p;

		vbq = &per_cpu(vmap_block_queue, i);
		spin_lock_init(&vbq->lock);
		INIT_LIST_HEAD(&vbq->free);
		p = &per_cpu(vfree_deferred, i);
		init_llist_head(&p->list);
		INIT_WORK(&p->wq, free_work);
	}

	/* Import existing vmlist entries. */
	for (tmp = vmlist; tmp; tmp = tmp->next) {    之前初始化io映射时的一些虚拟地址要放入
		va = kzalloc(sizeof(struct vmap_area), GFP_NOWAIT);  vmap_area红黑树队列
		va->flags = VM_VM_AREA;
		va->va_start = (unsigned long)tmp->addr;
		va->va_end = va->va_start + tmp->size;
		va->vm = tmp;
		__insert_vmap_area(va);  把相应内存块放入红黑树
	}

	vmap_area_pcpu_hole = VMALLOC_END;

	vmap_initialized = true;
}

上面函数比较简单,初始化了vmalloc中会用到的锁,等待队列等,然后把之前io空间相关的使用的虚拟内存地址放到vmap_area组成的红黑树中。

初始化完以后,可以用vmalloc进行分配内存了,vmalloc至少分配一页内存,页与页之间可以不连续,而且vmalloc分配的虚拟地址都位于比较高的地址,具体的分配过程参考下面的博文:

https://blog.csdn.net/oqqYuJi12345678/article/details/98115881

(27)sched_init();调度需要用到的一些数据结构的初始化
void __init sched_init(void)
{
	int i, j;
	unsigned long alloc_size = 0, ptr;

	init_rt_bandwidth(&def_rt_bandwidth,
			global_rt_period(), global_rt_runtime());


	for_each_possible_cpu(i) {
		struct rq *rq;

		rq = cpu_rq(i);设置cpu的调度队列rq
		raw_spin_lock_init(&rq->lock);
		rq->nr_running = 0;
		rq->calc_load_active = 0;
		rq->calc_load_update = jiffies + LOAD_FREQ;
		init_cfs_rq(&rq->cfs); 初始化普通进程调度队列cfs
		init_rt_rq(&rq->rt, rq); 初始化实时进程调度队列rt

		rq->rt.rt_runtime = def_rt_bandwidth.rt_runtime;

		for (j = 0; j < CPU_LOAD_IDX_MAX; j++)
			rq->cpu_load[j] = 0;

		rq->last_load_update_tick = jiffies;

		init_rq_hrtick(rq);
		atomic_set(&rq->nr_iowait, 0);
	}

	set_load_weight(&init_task);



#ifdef CONFIG_RT_MUTEXES
	plist_head_init(&init_task.pi_waiters);
#endif

	/*
	 * The boot idle thread does lazy MMU switching as well:
	 */
	atomic_inc(&init_mm.mm_count);
	enter_lazy_tlb(&init_mm, current);

	/*
	 * Make us the idle thread. Technically, schedule() should not be
	 * called from this thread, however somewhere below it might be,
	 * but because we are the idle thread, we just pick up running again
	 * when this runqueue becomes "idle".
	 */
	init_idle(current, smp_processor_id());设置init_task进程为idle进程

	calc_load_update = jiffies + LOAD_FREQ;

	/*
	 * During early bootup we pretend to be a normal task:
	 */
	current->sched_class = &fair_sched_class;

	init_sched_fair_class();

	scheduler_running = 1;
}

进行linux 初始化的这个进程,叫做init_task,对于每个cpu,都有一个rq队列,来维护进程的调度,其中进程又分为普通进程和实时进程,普通进程的队列为cfs,用红黑树算法进行管理,实时进程的队列为rt,关于进程调度,找到一个写的很好的系列博文,先放在这边,以后再详细研究进程调度原理:

http://blog.chinaunix.net/uid-20671208-id-4909620.html

(28)preempt_disable();关闭抢断

里面看起来只有一个内存屏障函数barrier,而且我做实验的是单核cpu,不会产生抢占,更复杂的以后再分析把,先把最重要的知识点连成一个线,再慢慢扩散开来。

(29)idr_init_cache();

IDR机制在Linux内核中指的是整数ID管理机制。实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。例如,在IIC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体,和驱动程序。将ID号和设备结构体结合起来,如果使用数组进行索引,一旦ID号很大,则用数组索引会占据大量内存空间。这显然不可能。或者用链表,但是,如果总线中实际存在的设备很多,则链表的查询效率会很低。此时,IDR机制应运而生。该机制内部采用红黑树实现,可以很方便的将整数和指针关联起来,并且具有很高的搜索效率。这边初始化名字叫idr_layer_cache的slab,为idr机制分配做准备,关于该机制更详细的说明,参考:

https://blog.csdn.net/midion9/article/details/50923095

(30)perf_event_init();

简单来说,perf是一种性能监测工具,它首先对通用处理器提供的performance counter进行编程,设定计数器阈值和事件,然后性能计数器就会在设定事件发生时递增计数器,直至这个计数器的计数值达到阈值,在不同的结构中对于计数器数值的提取有不同的方式,我们这边是空函数,没用这个功能。

(31)rcu_init();

rcu是Read-copy update的简写,RCU的updater(写者)会先复制一份指针指向的数据进行修改,然后修改指针指向修改后的数据,然后等待不再有读者在读取原来的数据,替换该数据指针,针对写入次数少,但读次数多的场景,读不需要等待,减少开销。

(32)tick_nohz_init();没有定义,暂不分析
(33)radix_tree_init();

基数树的初始化,

linux 内存管理通过radix树跟踪绑定到地址映射上的核心页,

该radix树允许内存管理代码快速查找标识为dirty或writeback的页。Linux radix树的API函数在lib/radix-tree.c中实现。

(34)early_irq_init();
int __init early_irq_init(void)
{
	int count, i, node = first_online_node;
	struct irq_desc *desc;

	init_irq_default_affinity();

	printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);

	desc = irq_desc;
	count = ARRAY_SIZE(irq_desc);

	for (i = 0; i < count; i++) {
		desc[i].kstat_irqs = alloc_percpu(unsigned int);
		alloc_masks(&desc[i], GFP_KERNEL, node);
		raw_spin_lock_init(&desc[i].lock);
		lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
		desc_set_defaults(i, &desc[i], node, NULL);
	}
	return arch_early_irq_init();
}

系统中断描述符的初始化

(35)init_IRQ();

具体machine相关的中断控制器的初始化

void __init init_IRQ(void)
{
	if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
		irqchip_init();
	else
		machine_desc->init_irq();
}

更详细的说明参考这篇文章:https://blog.csdn.net/oqqYuJi12345678/article/details/99726927

(36)tick_init();
(37)init_timers();
(38)hrtimers_init();
(39)timekeeping_init();
(40)time_init();

 上面函数都是定时器相关的初始化,可以参考下面的文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/100127745

(41)softirq_init();

软中断的初始化,参考下面的文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/99771140

(42)local_irq_enable();

打开中断

(43)kmem_cache_init_late()

 slab初始化后期,完善slab分配器的缓存机制,参考下面的文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/100173377

(44)console_init();

 串口的初始化

(45)setup_per_cpu_pageset()

 per_cpu_page缓存管理结构换成动态申请的,参考下面的文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/100526720

(46)calibrate_delay();

 主要用于udelay中延时计算,表示的是一个tick时间,需要loop多少次,更详细的分析,参考下面这篇文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/100548846

(47)pidmap_init();

pid相关的初始化,参考下面这篇文章:

https://blog.csdn.net/oqqYuJi12345678/article/details/102227977

(48)anon_vma_init();

创建anon_vma的slab缓存

(49)cred_init();

创建任务信用系统的slab高速缓存

(50)fork_init(totalram_pages);

根据当前物理内存计算出来可以创建进程(线程)的最大数量,并进行进程环境初始化,为task_struct创建slab 缓存

void __init fork_init(unsigned long mempages)
{
#ifndef CONFIG_ARCH_TASK_STRUCT_ALLOCATOR
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN	L1_CACHE_BYTES
#endif
	/* create a slab on which task_structs can be allocated */
	task_struct_cachep =
		kmem_cache_create("task_struct", sizeof(struct task_struct),
			ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);
   //创建task_struct的slab 机制
#endif

	/* do the arch specific task caches init */
	arch_task_cache_init(); //arm 架构并没有定义该函数

	/*
	 * The default maximum number of threads is set to a safe
	 * value: the thread structures can take up at most half
	 * of memory.
	 */
//根据 内存大小计算最大线程数量
	max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);

	/*
	 * we need to allow at least 20 threads to boot a system
	 */
	if (max_threads < 20)
		max_threads = 20;

	init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;
	init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
	init_task.signal->rlim[RLIMIT_SIGPENDING] =
		init_task.signal->rlim[RLIMIT_NPROC];
}
(51)proc_caches_init();

sighand_cache,signal_cache,files_cache,fs_cache,mm_struct等各种slab缓存的初始化。

(52)buffer_init();

buffer_head slab缓存初始化,该缓存应该是文件系统读写缓存,并设置了最大可用缓存。

(53)signals_init();

创建信号队列slab高速缓存

(54)page_writeback_init();

内核回写机制初始化

(55)proc_root_init();

proc相关的初始化,参照这篇文章

https://blog.csdn.net/oqqYuJi12345678/article/details/102527350

(56)rest_init()

做剩下的一些初始化工作

你可能感兴趣的:(linux3.10 系统start_kernel初始化流程详解(二))