zoukankan      html  css  js  c++  java
  • 2020-2021-1 20209309《Linux内核原理与分析》第四周作业

    Linux内核源码目录结构

    • arch:与CPU体系结构相关的子目录列表
    • block:存放Linux存储体系中关于块设备管理的代码
    • crypto:存放常见的加密算法的C语言代码
    • Documentation:用于存放文档
    • drivers:驱动目录,里面分门别类地存放了Linux内核支持的所有硬件设备的驱动源代码
    • firmware:固件目录
    • fs:文件系统,列出了Linux支持的各种文件系统的实现。
    • include:头文件目录,存放公共的头文件
    • init:存放Linux内核启动时的初始化代码
    • ipc:ipc目录里面是Linux支持的进程间通信的代码实现
    • kernel:存放内核本身需要的一些核心代码文件,包括进程号pid等
    • lib:公用的库文件
    • mm:与内存管理相关
    • net:存放网络相关的代码

    调试Linux内核启动过程

    使用gdb跟踪调试内核

    断点调试与按步运行


    启动过程分析(start_kernel)

    asmlinkage __visible void __init start_kernel(void)
    {
    	char *command_line;
    	char *after_dashes;
    	lockdep_init();
    	set_task_stack_end_magic(&init_task);
    	smp_setup_processor_id();
    	debug_objects_early_init();
    	boot_init_stack_canary();
    	cgroup_init_early();
    	local_irq_disable();
    	early_boot_irqs_disabled = true;
    	boot_cpu_init();
    	page_address_init();
    	pr_notice("%s", linux_banner);
    	setup_arch(&command_line);
    	mm_init_cpumask(&init_mm);
    	setup_command_line(command_line);
    	setup_nr_cpu_ids();
    	setup_per_cpu_areas();
    	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */
    	build_all_zonelists(NULL, NULL);
    	page_alloc_init();
    	pr_notice("Kernel command line: %s
    ", boot_command_line);
    	parse_early_param();
    	after_dashes = parse_args("Booting kernel",
    				  static_command_line, __start___param,
    				  __stop___param - __start___param,
    				  -1, -1, &unknown_bootoption);
    	if (!IS_ERR_OR_NULL(after_dashes))
    		parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
    			   set_init_arg);
    
    	jump_label_init();
    	setup_log_buf(0);
    	pidhash_init();
    	vfs_caches_init_early();
    	sort_main_extable();
    	trap_init();
    	mm_init();
    	sched_init();
    	preempt_disable();
    	if (WARN(!irqs_disabled(),
    		 "Interrupts were enabled *very* early, fixing it
    "))
    		local_irq_disable();
    	idr_init_cache();
    	rcu_init();
    	context_tracking_init();
    	radix_tree_init();
    	early_irq_init();
    	init_IRQ();
    	tick_init();
    	rcu_init_nohz();
    	init_timers();
    	hrtimers_init();
    	softirq_init();
    	timekeeping_init();
    	time_init();
    	sched_clock_postinit();
    	perf_event_init();
    	profile_init();
    	call_function_init();
    	WARN(!irqs_disabled(), "Interrupts were enabled early
    ");
    	early_boot_irqs_disabled = false;
    	local_irq_enable();
    	kmem_cache_init_late();
    	console_init();
    	if (panic_later)
    		panic("Too many boot %s vars at `%s'", panic_later,
    		      panic_param);
    
    	lockdep_info();
    	locking_selftest();
    
    #ifdef CONFIG_BLK_DEV_INITRD
    	if (initrd_start && !initrd_below_start_ok &&
    	    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
    		pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.
    ",
    		    page_to_pfn(virt_to_page((void *)initrd_start)),
    		    min_low_pfn);
    		initrd_start = 0;
    	}
    #endif
    	page_cgroup_init();
    	debug_objects_mem_init();
    	kmemleak_init();
    	setup_per_cpu_pageset();
    	numa_policy_init();
    	if (late_time_init)
    		late_time_init();
    	sched_clock_init();
    	calibrate_delay();
    	pidmap_init();
    	anon_vma_init();
    	acpi_early_init();
    #ifdef CONFIG_X86
    	if (efi_enabled(EFI_RUNTIME_SERVICES))
    		efi_enter_virtual_mode();
    #endif
    #ifdef CONFIG_X86_ESPFIX64
    	init_espfix_bsp();
    #endif
    	thread_info_cache_init();
    	cred_init();
    	fork_init(totalram_pages);
    	proc_caches_init();
    	buffer_init();
    	key_init();
    	security_init();
    	dbg_late_init();
    	vfs_caches_init(totalram_pages);
    	signals_init();
    	page_writeback_init();
    	proc_root_init();
    	cgroup_init();
    	cpuset_init();
    	taskstats_init_early();
    	delayacct_init();
    	check_bugs();
    	sfi_init_late();
    	if (efi_enabled(EFI_RUNTIME_SERVICES)) {
    		efi_late_init();
    		efi_free_boot_services();
    	}
    	ftrace_init();
    	rest_init();
    }
    

    宏定义

    asmlinkage宏定义作用主要是让传送给函数的参数全部使用栈式传送,不用寄存器来传送,由于寄存器数量有限,使用栈可以容纳更多参数。
    __init用来标志这个函数编译出来的目标代码具体放在那一段里。

    lockdep_init

    这个函数主要作用是初始化锁的状态跟踪模块,内核中使用锁来进行多线程与多处理器的同步操作,该函数用于调试内核加锁顺序,检测死锁可能。

    set_task_stack_end_magic

    该函数设置整个系统的第一个进程,其中的参数init_task为系统创建的第一个进程,也就是第0号进程,是唯一没有通过fork或kernel_thread产生的进程,是进程列表的第一个。

    smp_setup_processor_id

    该函数目的是直接获取对应多处理器的ID,相比于smp_processor_id函数其不需要使用setup_arch函数进行初始化。

    debug_objects_early_init

    函数主要作用是对调试对象进行早期的初始化,大致是对HASH锁和静态对象池进行初始化。

    cgroup_init_early

    这个函数主要作用是控制组进行早期的初始化,而控制组就是定义一组进程具有相同资源的占有程度。

    local_irq_disable

    这个函数主要作用是关闭当前CPU的所有中断响应。

    early_boot_irqs_off

    这个函数主要作用是标记内核还在早期初始化代码阶段,并且中断在关闭状态,如果有任何中断打开或请求中断的事情出现,都是会提出警告,以便跟踪代码错误情况。等到早期代码初始化结束之后,就会调用函数early_boot_irqs_on来设置这个标志为真。

    boot_cpu_init

    这个函数主要作用是设置当前引导系统的CPU在物理上存在,在逻辑上可以使用,并且初始化准备好。

    page_address_init

    初始化高端内存的映射表,在32位机中系统仅能访问4G,内核能访问的空间就更小了,所以为了映射更多的空间在原有内存的基础上分出高端内存,以访问更多的物理内存空间。

    setup_arch

    对内核架构进行初始化。再次获取CPU类型和系统架构,分析引导程序传入的命令行参数,进行页面内存初始化,处理器初始化,中断早期初始化等等。

    mm_init_owner

    设置最开始的初始化任务属于init_mm内存,而init_mm为mm_struct内存描述符的结构体实例。

    setup_command_line

    保存命令行,以便后面可以使用,与前面声明的command_line指针对应,这个指针是指向命令行参数的指针,主要用来指向引导程序传送给内核的命令行参数,函数setup_arch和函数setup_command_line会对它进行处理。

    setup_nr_cpu_ids

    设置最多有多少个nr_cpu_ids结构,nr_cpu_ids是一个特殊的值,在单CPU情况下是1,而SMP情况下,则为一个全局变量。

    setup_per_cpu_areas

    设置SMP体系每个CPU使用的内存空间,同时拷贝初始化段里数据。

    smp_prepare_boot_cpu

    为SMP系统里引导CPU进行准备工作。

    build_all_zonelists

    初始化所有内存管理节点列表,以便后面进行内存管理初始化。

    page_alloc_init

    设置内存页分配通知器。

    pidhash_init

    初始化进程ID的hash表,以便通过PID进行高效访问进程结构的信息。LINUX里共有四种类型的PID,因此就有四种HASH表相对应。

    vfs_caches_init_early

    虚拟文件系统的缓存初始化。

    sort_main_extable

    对内核内部的异常表进行堆排序,以便加速访问。

    trap_init

    对中断向量进行初始化。

    mm_init

    标记那些内存可以使用,并且告诉系统还有多少内存(除内存使用的)可以使用。

    sched_init

    对进程调度器进行初始化,比如分配调度器占用的内存,初始化任务队列,为当前任务设置空线程。

    preempt_disable

    关闭优先级调度。由于每个进程任务都有优先级,目前系统还没有完全初始化,还不能打开优先级调度。

    rcu_init

    初始化直接读拷贝更新的锁机制。

    radix_tree_init

    初始化radix树,radix树是基于二进制键值的查找树。

    init_IRQ

    初始化中断相关的工作,主要初始化中断描述数组,然后调用每个CPU架构中断初始化。

    prio_tree_init

    初始化优先搜索树,主要用在内存反向搜索方面。

    init_timers

    初始化引导CPU的时钟相关的数据结构,注册时钟的回调函数,当时钟到达时可以回调时钟处理函数,最后初始化时钟软件中断处理。

    hrtimers_init

    初始化高精度的定时器。

    softirq_init

    初始化软件中断,软件中断是使用线程来监视中断信号,而硬件中断是使用CPU硬件来监视中断。

    timekeeping_init

    设置初始化系统时钟计时,初始化内核里与时钟计时相关的变量。

    time_init

    初始化系统时钟。

    profile_init

    分配内核性能统计保存的内存,以便统计的性能变量可以保存到这里。

    early_boot_irqs_on

    设置内核还在早期初始化阶段的标志,以便用来调试时输出信息,与上面的early_boot_irqs_off对应。

    local_irq_enable

    打开CPU的中断,允许本CPU处理中断事件,与上面的local_irq_disable对应。

    console_init

    初始化控制台,从这个函数之后就可以输出内容到控制台了,这个函数初化之前,都没有办法输出内容,就是输出,也是写到输出缓冲区里,缓存起来,等到这个函数调用之后,就立即输出内容。

    lockdep_info

    打印锁的依赖信息,用来调试锁,这个函数可以查看目前锁的状态,以便可以发现那些锁产生死锁,那些锁使用有问题。

    locking_selftest

    用来测试锁的API是否使用正常,进行自我测试。比如测试自旋锁、读写锁、一般信号量和读写信号量。

    idr_init_cache

    创建IDR机制的内存缓存对象,IDR就是整数标识管理机制,用于管理整数的ID与对象的指针的关系,由于这个ID可以达到32位,也就是说,如果使用线性数组来管理,那么分配的内存太大了;如果使用线性表来管理,又效率太低了,所以就引用IDR管理机制来实现这个需求。

    setup_per_cpu_pageset

    创建每个CPU的高速缓存集合数组。因为每个CPU都不定时需要使用一些页面内存和释放页面内存,为了提高效率,就预先创建一些内存页面作为每个CPU的页面集合。

    numa_policy_init

    初始化NUMA的内存访问策略。NUMA(NonUniform Memory AccessAchitecture)主要用来提高多个CPU访问内存的速度。多个CPU访问同一个节点的内存速度远远比访问多个节点的速度来得快。

    calibrate_delay

    主要计算CPU需要校准的时间,对应的时间是CPU执行时间。

    thread_info_cache_init

    初始化线程信息的缓存。

    fork_init

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

    proc_caches_init

    进程缓存初始化。

    buffer_init

    初始化文件系统的缓冲区,并计算最大可以使用的文件缓存。

    key_init

    初始化安全键管理列表和结构。

    security_init

    初始化安全管理框架,以便提供访问文件/登录等权限。

    vfs_caches_init

    对虚拟文件系统进行缓存初始化,提高虚拟文件系统的访问速度。

    signals_init

    初始化信号队列缓存。

    cgroup_init

    初始化进程控制组,主要用来为进程和其子程提供性能控制。比如限定这组进程的CPU使用率.

    taskstats_init_early

    初始化任务状态相关的缓存、队列和信号量。任务状态主要向用户提供任务的状态信息。

    delayacct_init

    初始化每个任务延时计数。当一个任务等CPU运行,或者等IO同步时,都需要计算等待时间。

    启动过程分析(rest_init)

    static noinline void __init_refok rest_init(void)
    {
    	int pid;
    	rcu_scheduler_starting();
    	kernel_thread(kernel_init, NULL, CLONE_FS);
    	numa_default_policy();
    	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    	rcu_read_lock();
    	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    	rcu_read_unlock();
    	complete(&kthreadd_done);
    	init_idle_bootup_task(current);
    	schedule_preempt_disabled();
    	cpu_startup_entry(CPUHP_ONLINE);
    }
    

    rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd。之后调用schedule函数开启内核的调度系统,使系统开始运转。最终其结束了整个内核的启动,以一个死循环的方式保持系统运行,linux内核最终的状态是:有任务的时候去执行各个进程任务,没有任务执行就运行死循环(空闲进程)维持。

    • 进程0:进程0其实就是空闲进程(死循环)以维持系统运行。
    • 进程1:kernel_init函数就是进程1,这个进程被称为init进程,也是第一个用户进程。
    • 进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。它的作用是管理调度其他内核进程,保证linux内核自身能正常工作的。
  • 相关阅读:
    SQL数据去重复 Distinct 和 row_number() over()
    Excel闪退问题解决
    SQL Server 修改服务器登录名称以及密码
    从底层角度看ASP.NET-A low-level Look at the ASP.NET Architecture
    MD5加密
    MD5实例化异常 未能执行FIPS验证加密算法
    JDBC(上)
    自学MySQL第二天
    自学MySQL第一天
    自学JavaWeb第五天jQuery进阶
  • 原文地址:https://www.cnblogs.com/yanzs/p/13910344.html
Copyright © 2011-2022 走看看