(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()
做剩下的一些初始化工作