Chinaunix首页 | 论坛 | 博客
  • 博客访问: 170449
  • 博文数量: 109
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 147
  • 用 户 组: 普通用户
  • 注册时间: 2015-01-23 16:12
文章分类

全部博文(109)

文章存档

2015年(109)

我的朋友

分类: LINUX

2015-01-23 16:17:27

[原创]Linux arm 启动 c语言部分详解第五讲

written by leeming

欠下的债始终是要还的,过年前基本把linux c语言启动部分看了下,对于工程来说(如何移植一个新内核,为什么要这样移植已经能够讲明白了),后来继续深入下去的时候发现没有linux内存管理,进程管理的知识的话下面的内容根本无法看下去了。

我今天也没打算把这部分讲解,只准备稍微带一下(这部分水太深了,我对其的理解也不透彻,而且我向来认为抓住系统的框架主线才是对理解最有帮助的,具体的细节可以慢慢研读)。

继续,上一讲到了start_kerneltime_init函数部分(同样以下黑色是主线,蓝色是函数分支线):

/*

        * HACK ALERT! This is early. We're enabling the console before

        * we've done PCI setups etc, and console_init() must be aware of

        * this. But we do want output early, in case something goes wrong.

        */

       console_init();

{

initcall_t *call;

 

       /* Setup the default TTY line discipline. */

      

       /*****************************************************************************/

      

       /*这段code主要的用途就是

      

       注册tty线路规程的,大家研究tty的驱动就会发现了在用户和硬件之间tty的驱动是分了三层的

      

       最底层当然是tty驱动程序了,主要负责从硬件接受数据,和格式化上层发下来的数据后给硬件。

      

       在驱动程序之上就是线路规程了,他负责把从tty核心层或者tty驱动层接受的数据进行特殊的按着某个协议的格式化,就像是

      

       ppp或者蓝牙协议,然后在分发出去的。

      

       tty线路规程之上就是tty核心层了。

       */

 

       (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);

 

       /*

        * set up the console device so that later boot sequences can

        * inform about problems etc..

        */

#ifdef CONFIG_EARLY_PRINTK

       disable_early_printk();

#endif

       /*

       vmlinux.lds.S中连接脚本汇编中有这段代码

       __con_initcall_start = .;

       *(.con_initcall.init)

       __con_initcall_end = .;

       因此我们再此处调用的就是con_initcall.init段的代码,

       fn函数放到.con_initcall.init的输入段中,如下:

       #define console_initcall(fn) \

       static initcall_t __initcall_##fn \

       __attribute_used__ __attribute__((__section__(".con_initcall.init")))=fn

       */

       //在我们串口驱动里面有这么一个注册语句:console_initcall(serial8250_console_init);

       //因此我们的控制台初始化流程就是:start_kernel->console_init->serial8250_console_init

       call = __con_initcall_start;

       while (call < __con_initcall_end) {

              (*call)();

              call++;

       }

}

       if (panic_later)

              panic(panic_later, panic_param);

       profile_init();

{

       if (!prof_on)

              return;

 

       /* only text is profiled */

       //profile是用来对系统剖析的,在系统调试的时候有用

       //需要打开内核选项,并且在bootargs中有profile这一项才能开启这个功能

       /*

       profile只是内核的一个调试性能的工具,这个可以通过menuconfigprofiling support打开。

       1. 如何使用profile

       首先确认内核支持profile,然后在内核启动时加入以下参数:profile=1或者其它参数, 新的内核支持profile=schedule 1

       2. 内核启动后会创建/proc/profile文件,这个文件可以通过readprofile读取,

       readprofile -m /proc/kallsyms | sort -nr > ~/cur_profile.log,

       或者readprofile -r -m /proc/kallsyms |sort -nr,

       或者readprofile -r && sleep 1 && readprofile -m /proc/kallsyms |sort -nr >~/cur_profile.log

       3. 读取/proc/profile可获得哪些内容?

       根据启动配置profile=?的不同,获取的内容不同:

       如果配置成profile=? 可以获得每个函数执行次数,用来调试函数性能很有用

       如果设置成profile=schedule ?可以获得每个函数调用schedule的次数,用来调试schedule很有用

       */

       prof_len = (_etext - _stext) >> prof_shift;

       prof_buffer = alloc_bootmem(prof_len*sizeof(atomic_t));

}

       //只是打开了cpsrI(I = 0)

       local_irq_enable();

      

/*注意熊出没(红色部分太过恶心,暂时不讲解)*/

       /* 初始化dentryinode缓冲队列的hash,这一部分是和文件系统相关的,以后再仔细看 */

       vfs_caches_init_early();

       cpuset_init_early();

      

       /* 最后内存初始化,释放前边标志为保留的所有页面 ,这个函数结束之后就不能使用alloc_bootmem**等申请地段内存的函数了*/

       mem_init();

       /* slab初始化 */

       kmem_cache_init();

       setup_per_cpu_pageset();

       /*初始化非统一内存访问系统中的内存的策略,一般用于多处理器系统,在我们这里为空*/

       numa_policy_init();

 

       //不同的architecture可以重载这个函数,默认为null

       if (late_time_init)

       late_time_init();

       //这段代码很有意思,可以细读一下,看看Linus的代码

       calibrate_delay();

      

       pidmap_init();

       pgtable_cache_init();

       prio_tree_init();

       anon_vma_init();

#ifdef CONFIG_X86

       if (efi_enabled)

              efi_enter_virtual_mode();

#endif

 

       //num_physpages是在mem_init函数中赋值的

       fork_init(num_physpages);

       proc_caches_init();

       buffer_init();

       unnamed_dev_init();

       key_init();

       security_init();

       vfs_caches_init(num_physpages);

       radix_tree_init();

       signals_init();

       /* rootfs populating might need page-writeback */

       page_writeback_init();

 

#ifdef CONFIG_PROC_FS

       proc_root_init();

{

       //proc文件系统索引节点创建高速缓存内存描述结构

       int err = proc_init_inodecache();

       if (err)

              return;

       //注册proc文件系统

       err = register_filesystem(&proc_fs_type);

       if (err)

              return;

       /*该函数基本完成三个步骤,首先调用read_super()函数,在这个函数里,

       VFS将为proc文件系统分配一个超级块结构,并设置s_devs_flags等域,然后,

       将调用proc文件系统的自己的read_super例程,对应proc文件系统,该例程是proc_read_super()

       该例程将设置超级块结构的其他值。我们将在下一节进行分析。

其次,使用add_vfsmnt()函数建立proc文件系统的vfsmount结构,并将其加入到已装载文件系统的链表中(可参考图-xx)。

最后,返回该vfsmount结构,并利用返回值,使用指针proc_mnt指向该vfsmount结构。*/

       proc_mnt = kern_mount(&proc_fs_type);

       err = PTR_ERR(proc_mnt);

       if (IS_ERR(proc_mnt)) {

              unregister_filesystem(&proc_fs_type);

              return;

       }

 

       /*创建一些文件,通过create_proc_read_entrycreate_seq_entry创建

       可读文件和读写文件*/

       proc_misc_init();

 

       //利用proc_mkdir创建目录,也可以通过symlinks 以及 proc_symlink 实现

       proc_net = proc_mkdir("net", NULL);

       proc_net_stat = proc_mkdir("net/stat", NULL);

 

#ifdef CONFIG_SYSVIPC

       proc_mkdir("sysvipc", NULL);

#endif

#ifdef CONFIG_SYSCTL

       proc_sys_root = proc_mkdir("sys", NULL);

#endif

#if defined(CONFIG_BINFMT_MISC) || defined(CONFIG_BINFMT_MISC_MODULE)

       proc_mkdir("sys/fs", NULL);

       proc_mkdir("sys/fs/binfmt_misc", NULL);

#endif

       proc_root_fs = proc_mkdir("fs", NULL);

       proc_root_driver = proc_mkdir("driver", NULL);

       proc_mkdir("fs/nfsd", NULL); /* somewhere for the nfsd filesystem to be mounted */

#if defined(CONFIG_SUN_OPENPROMFS) || defined(CONFIG_SUN_OPENPROMFS_MODULE)

       /* just give it a mountpoint */

       proc_mkdir("openprom", NULL);

#endif

       proc_tty_init();

#ifdef CONFIG_PROC_DEVICETREE

       proc_device_tree_init();

#endif

       proc_bus = proc_mkdir("bus", NULL);

}

#endif

       //用于smp架构,我们没有

       cpuset_init();

 

       //不同架构对这一段的定义不同,在arm中是验证内存一致性

       check_bugs();

主要调用了check_writebuffer_bugs函数

{

       struct page *page;

       const char *reason;

       unsigned long v = 1;

 

       printk(KERN_INFO "CPU: Testing write buffer coherency: ");

       //申请一页空间,并将该页的struct page指针给page

       page = alloc_page(GFP_KERNEL);

       if (page) {

              unsigned long *p1, *p2;

              //配置二级页表描述符

              pgprot_t prot = __pgprot(L_PTE_PRESENT|L_PTE_YOUNG|

                                    L_PTE_DIRTY|L_PTE_WRITE|

                                    L_PTE_BUFFERABLE);

 

              //page所指向的物理页映射为两个不同的虚拟地址

              //p1, p2

              p1 = vmap(&page, 1, VM_IOREMAP, prot);

              p2 = vmap(&page, 1, VM_IOREMAP, prot);

 

              if (p1 && p2) {

                     //通过向p1p2写数据来验证,返回0表明正确

                     v = check_writebuffer(p1, p2);

                     reason = "enabling work-around";

              } else {

                     reason = "unable to map memory\n";

              }

 

              //取消这两块虚拟地址的映射

              vunmap(p1);

              vunmap(p2);

              put_page(page);

       } else {

              reason = "unable to grab page\n";

       }

 

       if (v) {

              printk("failed, %s\n", reason);

              shared_pte_mask |= L_PTE_BUFFERABLE;

       } else {

              printk("ok\n");

       }

}

       //arm架构,用不起来ACPI

       acpi_early_init(); /* before LAPIC and SMP init */

       /* Do the rest non-__init'ed, we're now alive */

       rest_init();

{

//新建init进程,也就是1号进程,原来的为0号进程

//linux进程流程:0号内核进程->1号内核进程->1号用户进程

       kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);

      

       numa_default_policy();

       unlock_kernel();

       /*

        * The boot idle thread must execute schedule()

        * at least one to get things moving:

        */

        

        //preempt是开启抢占式内核的时候才生效的

       preempt_enable_no_resched();

       //进入idle之前,主动调用下schedule,看有没有其他进程

       schedule();

       preempt_disable();

 

       /* Call into cpu_idle with preempt disabled */

       cpu_idle();

}

 

阅读(603) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~