此生既入苦寒山,何妨再攀险峰!
分类:
2011-08-24 11:36:10
[原创]Linux arm 启动 c语言部分详解第五讲
written by leeming
欠下的债始终是要还的,过年前基本把linux c语言启动部分看了下,对于工程来说(如何移植一个新内核,为什么要这样移植已经能够讲明白了),后来继续深入下去的时候发现没有linux内存管理,进程管理的知识的话下面的内容根本无法看下去了。
我今天也没打算把这部分讲解,只准备稍微带一下(这部分水太深了,我对其的理解也不透彻,而且我向来认为抓住系统的框架主线才是对理解最有帮助的,具体的细节可以慢慢研读)。
继续,上一讲到了start_kernel的time_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只是内核的一个调试性能的工具,这个可以通过menuconfig中profiling 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));
}
//只是打开了cpsr的I位(I = 0)
local_irq_enable();
/*注意熊出没(红色部分太过恶心,暂时不讲解)*/
/* 初始化dentry和inode缓冲队列的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_dev,s_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_entry与create_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) {
//通过向p1,p2写数据来验证,返回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();
}