搜档网
当前位置:搜档网 › Linux_源代码分析

Linux_源代码分析

Linux_源代码分析
Linux_源代码分析

Linux内核(2.6.13.2)源代码分析

苗彦超

摘要:

1系统启动

1.1汇编代码head.S及以前

设置CPU状态初值,创建进程0,建立进程堆栈:

movq init_rsp(%rip), %rsp,init_rsp定义

.globl init_rsp

init_rsp:

.quad init_thread_union+THREAD_SIZE-8

即将虚地址init_thread_union+THREAD_SIZE-8作为当前进程(进程0)核心空间堆栈栈底,init_thread_union定义于文件arch/x86_64/kernel/init_task.c中:

union thread_union init_thread_union __attribute__((__section__(".data.init_task"))) =

{INIT_THREAD_INFO(init_task)};

INIT_THREAD_INFO定义于文件include/asm-x86_64/thread_info.h中,初始化init_thread_union.task = &init_task,init_task同样定义于文件init_task.c中,初始化为:

struct task_struct init_task = INIT_TASK(init_task);

INIT_TASK宏在include/linux/init_task.h中定义。

全部利用编译时静态设置的初值,将进程0的控制结构设置完成,使进程0可以按普通核心进程访问。

init_task.mm = NULL; init_task.active_mm = INIT_MM(init_mm), init_https://www.sodocs.net/doc/0517577038.html,m = “swapper”

INIT_MM将init_mm.pgd初始化为swapper_pg_dir,即init_level4_pgt,定义与head.S中。进程0的名称为swapper。

利用下述汇编代码跳转到C函数执行:

movl %esi, %edi// 传递函数参数

movq initial_code(%rip),%rax

jmp *%rax

initial_code:

.quad x86_64_start_kernel

开始执行文件arch/x86_64/kernel/head64.c中的C函数x86_64_start_kernel(char * real_mode_data),1.2函数x86_64_start_kernel(char * real_mode_data)

1设置全部中断向量初始入口为early_idt_handler,加载中断描述符idt_descr

2clear_bss():BSS段清0

3pda_init(0):设置处理器0相关信息(processor datastructure area ?),重置CR3为init_level4_pgt 4copy_bootdata:复制BIOS启动参数到操作系统变量x86_boot_params中,再复制启动命令行参数由x86_boot_params到saved_command_line中,用printk显示saved_command_line,从此不再

与实模式数据打交道

5cpu_set:设置CPU 0 开始工作标志

6处理“earlyprintk=”、“numa”、“disableapic”等命令行参数

7setup_boot_cpu_data():设置CPU信息结构boot_cpu_data,使用cpuid指令

8执行start_kernel()函数

1.3start_kernel函数

1.3.1系统结构相关初始化前

lock_kernel():文件lib/kernel_lock.c实现了BKL(big kernel lock),使用:lock_kernel/unlock_kernel 如果开启PREEMPT_BKL,则使用信号量kernel_sem实现,否则使用自旋锁kernel_flag实现。通常缺省开启PREEMPT_BKL选项。

当task->lock_depth等于-1时,执行down(&kernel_sem)操作current->lock_depth++

unlock_kernel执行--current->lock和up(&kernel_sem)操作

page_address_init():在x86-64系统中为空函数。

printk(linux_banner):打印特征信息

1.3.2体系结构相关初始化setup_arch(&command_line)

9setup_memory_region():

I sanitize_e820_map(E820_MAP, &E820_MAP_NR):清理E820图,E820_MAP,E820_MAP_NR

均为x86_boot_params中的参数

II copy_e820_map():调用add_memory_region()函数将有效的地址区间加入到E820结构struct e820map e820中,最为系统E820图

III e820_print_map():调用printk显示最终的E820图及数据来源BIOS-e820、BIOS-e88或BIOS-e801

10copy_edd():如果开启编译选项EDD,则复制EDD信息由EDD_MBR_SIGNATURE到变量struct edd edd中,EDD_MBR_SIGNATURE由启动参数x86_boot_params设置。EDD:Enhanced Disk Dirve Services,参数将传递给drivers/firmware/edd.c,参考include/linux/edd.h

11设置init_mm、code_resource、data_resource信息

12parse_cmdline_early:分析早期使用的命令行参数

13再次设置CPU参数信息结构boot_cpu_data

14end_pfn = e820_end_of_ram():分析E820图,设置内存容量相关全局变量:end_user_pfn:启动参数mem=xx设置的页面数目;end_pfn_map:系统RAM(主存)页面数,即建立直接映射页表的页面数,可通过__va、__pa宏进行地址操作;end_pfn:操作系统直接管理的页面数

15check_efer:读取msr寄存器MSR_EFER,测试扩展特性extended feature register

16init_memory_mapping(0, end_pfn_map<

I find_early_table_space:根据映射内存总容量计算页表pud和pmd的需求量(2M页面,3级

页表)tables字节(页面容量PAGE_SIZE的整数倍),利用E820图,从物理地址8000h开始

寻找容量为tables字节的连续物理内存,并跳过区间[640KB,_end]的保留内存,通常情况下

寻找的结果start就是从8000h开始,设置全局变量table_end = table_start = start >>

PAGE_SHIFT,区间[table_start,table_end]即为直接映射页面表。

II建立区间[0, end_pfn_map<

III allow_low_page(&map, &pud_phys):使用物理页面table_end,并且table_end++,将分配的物理页面临时映射到虚地址40M或42M,使用临时页表temp_boot_pmds(定义于文件head.S)。

IV unmap_low_page(map)解除allow_low_page在40M或42M的临时映射

17acpi_boot_table_init(arch/i386/kernel/acpi/boot.c中):ACPI初始化

I acpi_table_init(drivers/acpi/tables.c):ACPI表初始化(Initialize the ACPI boot-time table parser)

i acpi_find_rsdp:定位RSDP(Root System Description Pointer)位置,

A acpi_scan_rsdp(0, 0x400):在区间[0,3FFh]搜索RDSP签字“RSD PTR”。

B acpi_scan_rsdp(0xE0000, 0x20000):在区间[E_0000h,F_FFFFh]搜索RDSP签字“RSD

PTR”。

C搜索成功返回签字所在地址,否则返回0。

ii通过printk显示“RSDP(rsdp->version,rsdp->oem_id,rsdp_phys)”信息。

iii acpi_table_compute_checksum:计算rsdp校验和

iv acpi_table_get_sdt(rsdp):以版本2.0及以上为例:

A std_pa = ((struct acpi20_table_rsdp*)rsdp)->xsdt_address:获取XSDT表物理地址

B header = __acpi_map_table(std_pa):获取ACPI表头虚地址,x86-64使用__va直接映

射,std_pa不超过8M时i386也使用__va直接映射,超过8M时使用固定映射

C mapped_xstd = __acpi_map_table(std_pa),映射整个XSDT(Extended System

Description Table)

D检查XSDT表头签名“XSDT”和校验和

E设置sdt_count和XSDT表中各条目物理地址到std_entry[i].pa中

F acpi_table_print(header, sdt_pa):用printk显示头部信息

G__acpi_map_table(sdt_entry[i].pa):对XSDT中的每个表项物理地址std_entry[i].pa作为一个acpi_table_header结构进行地址映射,调用acpi_table_print显示并计算校验和,

设置std_entry[i].size字段,将签名header->signature与数组acpi_table_signatures中的

名字比较,设置std_entry[i].id字段。数组acpi_table_signatures定义形式比较怪异。

H acpi_get_table_header_early:搜索ACPI_DSDT并调用acpi_table_print打印,但物理

地址不清楚,设置为0

II acpi_table_parse(ACPI_BOOT, acpi_parse_sbf):在sdt_entry中搜索ACPI_BOOT表项,并调用acpi_parse_sb(sdt_entry[?].pa, sdt_entry[?].size),映射sb = __acpi_map_table(sdt_entry[?].pa,

size),设置sbf_port = sb->sbf_cmos

III acpi_blacklisted(drivers/acpi/backlist.c):sdt_entry[*]中是否有表acpi_backlist[]中给出的ACPI ID,符合条件则给出错误并可能调用acpi_disable关闭acpi功能

18acpi_numa_init:需要开启编译选项ACPI_NUMA,

I acpi_table_parse(ACPI_SRAT, acpi_parse_srat):分析SRAT(System Resource Affinity Table)

II acpi_table_parse_srat(ACPI_SRAT_PROCESSOR_AFFINITY,acpi_parse_processor_affinity,NR_ CPUS);

i acpi_table_parse_madt_family(ACPI_SRAT, sizeof(struct acpi_table_srat),

ACPI_SRAT_PROCESSOR_AFFINITY, acpi_parse_processor_affinity, NR_CPUS):

A定位MADT,MADT在sdt_entry[*]中的ID:ACPI_SRAT

B查找MADT中ID= ACPI_SRAT_PROCESSOR_AFFINITY的表项,对每个表项调用函数acpi_parse_processor_affinity

C acpi_parse_processor_affinity:

a acpi_table_print_srat_entry:打印信息

b acpi_numa_processor_affinity_init(processor_affinity):

(1)pxm = processor_affinity->proximity_domain;

(2)setup_node(pxm):

(I)nodes_weight(nodes_found):最终调用generic_hweight64(nodes_found)

计算nodes_found中为1的bit个数

(II)fisrt_unset_node:查找第一个为0的bit序号node

(III)node_set(node, nodes_found)

(IV)pxm2node[pxm] = node

(3)cpu_to_node[num_processors] = node,acpi_numa = 1

(4)显示信息:printk(KERN_INFO "SRAT: PXM %u -> APIC %u -> CPU %u ->

Node %u\n", pxm, pa->apic_id, num_processors, node)

(5)增加处理器计数:num_processors++

III acpi_table_parse_srat(ACPI_SRAT_MEMORY_AFFINITY, acpi_parse_memory_affinity, NR_NODE_MEMBLKS),处理过程与上一步类似,只是最后一步调用函数

acpi_parse_memory_affinity,进而调用函数acpi_numa_memory_affinity_init,处理内存节点。

并设置nodes_parsed和nodes字段。注释中说主要用于IA64。

IV acpi_table_parse(ACPI_SLIT, acpi_parse_slit):分析SLIT(System Locality Information Table)V acpi_numa_arch_fixup:空函数

19开启编译选项NUMA调用函数numa_initmem_init(0, end_pfn),否则调用函数contig_initmem_init(0, end_pfn)

20numa_initmem_init:

I若开启ACPI_EMU编译选项,则执行numa_emulation(0, end_pfn),成功则numa_initmem_init返回;该选项主要用于调试

II若开启ACPI_NUMA编译选项,则执行acpi_scan_nodes(0, end_pfn<< PAGE_SHIFT),成功则numa_initmem_init返回;

i compute_hash_shift:计算memnode_shift

ii显示特征信息:printk(KERN_DEBUG"Using %d for the hash shift. Max adder is %lx \n",shift,maxend);

iii根据nodes字段设置,对每个节点调用函数setup_node_bootmem(i, nodes[i].start, nodes[i].end)

iv numa_init_array:设置cpu_to_node、node_to_cpumask等序号映射字段。

III若开启K8_NUMA编译选项,则执行k8_scan_nodes(0, end_pfn << PAGE_SHIFT),成功则numa_initmem_init返回;k8_scan_nodes中定义的结构最大支持8个NODE。

i find_northbirdgh:查找CPU中的北桥模块中的内存地址映射功能(功能0:

HyperTransport Technology Configuration,功能1:Address Map),(VendorID : DeviceID)

= (1022:1100/1101),返回设备号

ii特征信息:printk(KERN_INFO "Scanning NUMA topology in Northbridge %d\n", nb);

iii读北桥设备功能0(1022:1100)Offset 60h信息(NodeID),计算系统中的Node数目,即处理器数目。

iv显示表示信息:printk(KERN_INFO "Number of nodes %d\n", numnodes)

v读北桥设备功能1偏移量40h~7Ch,获取内存分布信息和各内存地址对应的nodeid,记录到局部变量nodes中,nodes[nodeid].start = base, nodes[nodeid].end = limit,在

nodes_parsed中标记有效nodeid。

vi memnode_shift = compute_hash_shift(nodes, numnodes)

A maxend = MAX{nodes[*].end}

B满足条件(1UL << shift ) < maxend / NODEMAPSIZE的最小shift值,NODEMAPSIZE=0xFF。返回shift

C对全部内存地址addr,以粒度(1UL<>shift]=i,i 为NODE号(0~7)

注:

memnode_shift:将总物理内存等分成255段,每段容量的移位数

memnodemap[0..254]:每等分段归属NODE号

vii标志信息:printk(KERN_INFO "Using node hash shift of %d\n", memnode_shift)

viii对于全部配置有物理内存的NODE:设置cpu_to_node[i] = i,setup_node_bootmem(i, nodes[i].start, nodes[i].end):

A start = round_up(start, ZONE_ALIGN):圆整起始地址,ZONE_ALIGN:

1<<(MAX_ORDER+PAGE_SHIFT) = 8MB

B标志信息:printk("Bootmem setup node %d %016lx-%016lx\n", nodeid, start, end) 注:struct mem_section mem_section[NR_MEM_SECTIONS(8192)],导出,共占用

64KB空间

C memory_present:对于本NODE的全部section,标记mem_section[section].

section_mem_map有效

D nodedata_phys = find_e820_area(start, end, pgdat_size):分配pg_data_t结构内存,

并页面容量对齐,从本NODE的内存中分配。

E node_data[nodeid] = phys_to_virt(nodedata_phys),node_data:64个分量

F node_data[nodeid]->bdata = &plat_node_bdata[nodeid](全局变量)

G node_data[nodeid]->node_start_pfn = start_pfn(本NODE起始页面号)

H node_data[nodeid]->node_spanned_pages = end_pfn - start_pfn(本NODE页面数)

I bootmap_pages = bootmem_bootmap_pages(end_pfn - start_pfn):计算本NODE全

部内存建立位图需要的页面数

J bootmap_start = round_up(nodedata_phys + pgdat_size, PAGE_SIZE):本NODE空闲物理内存页面容量对齐基地址

K bootmap_start = find_e820_area(bootmap_start, end, bootmap_pages<< PAGE_SHIFT):分配本NODE内存位图空间

L bootmap_size = init_bootmem_node(node_data[nodeid], bootmap_start >> PAGE_SHIFT, start_pfn, end_pfn),直接调用init_bootmem_core(pgdat,

freepfn/mapstart, startpfn, endpfn),参数按顺序直接结合

a bdata = node_data[nodeid]->bdata(前面已设置为&plat_node_bdata[nodeid])

b bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT)

c bdata->node_boot_start = (start << PAGE_SHIFT)(重置)

d bdata->node_low_pfn = end

e位图区bdata->node_bootmem_map全部设置为1,保留全部内存

f返回位图容量,8字节对齐

M e820_bootmem_free(node_data[nodeid], start, end):根据e820表,对于本节点全部属于E820_RAM、且e820图标志为有效的内存区域,调用函数free_bootmem_node

(node_data[nodeid], addr, last-addr),进一步直接调用free_bootmem_core

(node_data[nodeid]->bdata, physaddr, size),将全部有效页面在bdata->

node_bootmem_map中的对应比特清0,标记内存为空闲。

N保留pg_data_t结构占用的内存node_data[nodeid]

O保留位图占用内存bootmap_start

P在node_online_map中标记本NODE有效

ix numa_init_array:对于其它CPU(未配置有物理内存),设置cpu_to_node[i]值,并在node_online_map中标记本CPU对应的NODE号有效,设置node_to_cpumask

[cpu_to_node(0)]的bit0置1

IV使用No NUMA配置:

i memnode_shift = 63,memnodemap[0] = 0,node_online_map清0,仅NODE0有效

ii cpu_to_node[*] = 0,node_to_cpumask[0] = cpumask_of_cpu(0)

iii setup_node_bootmem(0, 0, end_pfn << PAGE_SHIFT):设置全部物理内存归NODE 0管理

21contig_initmem_init(0, end_pfn):

I memory_present(0, start_pfn, end_pfn)

II bootmap_size = bootmem_bootmap_pages(end_pfn)<

的位图容量

III bootmap = find_e820_area(0, end_pfn<

IV bootmap_size = init_bootmem(bootmap >> PAGE_SHIFT, end_pfn):

i max_low_pfn = pages,min_low_pfn = start

ii init_bootmem_core(NODE_DATA(0), start, 0, pages):设置位图

V e820_bootmem_free(NODE_DATA(0), 0, end_pfn << PAGE_SHIFT):根据e820图,释放全部有效内存

VI reserve_bootmem(bootmap, bootmap_size):保留位图内存

22reserve_bootmem_generic(table_start << PAGE_SHIFT, (table_end - table_start) << PAGE_SHIFT):保留直接映射页表内存

I int nid = phys_to_nid(phys):通过memnodemap[addr>>memnode_shift]得到nid

II reserve_bootmem_node(NODE_DATA(nid), phys, len):直接调用reserve_bootmem_core (pgdat->bdata, physaddr, size),bdata->node_bootmem_map中对应bit清0,标记为保留

23保留核心映像内存,即区域[1M,__pa(_end)],保留第0页物理内存

24reserve_ebda_region():保留EBDA区

25若开启SMP选项:保留第1页内存和trampoline区,即第6页

26若开启ACPI_SLEEP选项,acpi_reserve_bootmem:调用alloc_bootmem_low分配1页物理内存,并保存到acpi_wakeup_address中

I find_smp_config:直接调用find_intel_smp,利用函数smp_scan_config在区间[0,1K)、[639K,

640K)和[960K, 1024K)搜索SMP配置,成功则find_intel_smp返回,若失败则读取物理地址40Eh

数据addr,乘以16作为基地址base,再次调用函数smp_scan_config搜索区间[base, base+4K)

搜索SMP配置

II smp_scan_config:寻找以MP表签名“_MP_”起始的区域作为MP表,若校验和等参数匹配成功认为发现MP表,并保留该区域所在页面,置smp_found_config=1。若再进一步MP表中

有配置表地址,即签名后的第一个字段不为0,则保留配置表页面

27若开启BLK_DEV_INITRD选项,若initrd区域有效,则保留initrd区域,基地址:INITRD_START;

长度:INITRD_SIZE

28若开启KEXEC选项,则保留crashk_res指定的区域。该区域由启动参数“crashkernel=”指定,KEXEC注释:kexec is a system call that implements the ability to shutdown your current kernel, and to start another kernel. It is like a reboot but it is indepedent of the system firmware. And like a reboot you can start any kernel with it, not just Linux.

29sparse_init:开启编译选项SPARSEMEM有效,对于全部有效内存SECTIONS,执行下面操作:第pnum个SECTIOON,调用接口alloc_bootmem_node从本SECTION对应的NODE内存中为其分配一个map区域,为每个页面设置一个page结构,设置mem_section[pnum]. section_mem_map |= map - section_nr_to_pfn(pnum)

30paging_init(NUMA结构):根据node_possible_map结构,对于每个有效的NODE i,调用函数setup_node_zones(i):

I start_pfn、end_pfn:本NODE内存起、止页面号,可能存在空洞

II设置zones[ZONE_DMA]、zones[ZONE_NORMAL]:内存区间[start_pfn,end_pfn]在e820中有效容量

III设置holes[ZONE_DMA]、holes[ZONE_NORMAL]:内存区间[start_pfn,end_pfn]在e820中无效容量,即本NODE内的内存空洞

注:若start_pfn ≥ dma_end_pfn(16M),则zones[ZONE_DMA]、holes[ZONE_DMA]都为0 IV free_area_init_node(nodeid, NODE_DATA(nodeid), zones, start_pfn, holes)

i pgdat = NODE_DATA(nodeid)

ii pgdat->node_id = nid; pgdat->node_start_pfn = node_start_pfn;

iii calculate_zone_totalpages(pgdat, zones_size, zholes_size):

A重置pgdat->node_spanned_pages = SUM{zones_size[*]},与原值相同

B设置pgdat->node_present_pages = SUM{zones_size[*]} – SUM{holes_size[*]}

C标志信息:printk(KERN_DEBUG "On node %d totalpages: %lu\n", pgdat->node_id, realtotalpages)

iv alloc_node_mem_map(pgdat)

A如果开启编译选项FLAT_NODE_MEM_MAP,则执行下述操作,否则为空函数

B设置pgdat->node_mem_map = alloc_bootmem_node(pgdat, size),在本NODE内存中分配内存,用于保存page结构

C若进一步开启编译选项FLAGMEM,且pgdat = NODE_DATA(0)则设置mem_map = NODE_DATA(0)->node_mem_map

v free_area_init_core(pgdat, zones_size, zholes_size):初始化队列头部pgdat->kswapd_wait,设置pgdat->kswapd_max_order = 0,对于本NODE内部的每个zone(x86-64最多只有2

个有效zone),执行下述操作:

A将本zone内的实际页面数加入到nr_kernel_pages、nr_all_pages中

B设置zone->spanned_pages为包含空洞的页面数,zone->presend_pages为实际有效页面数

C设置zone的名字:zone->name指向zone_names

D设置各CPU单页面管理集:zone->pageset[cpu] = &boot_pageset[cpu],调用函数setup_pageset进行初始化:pcp->high:255*6,pcp->low:255*2,pcp->batch = 255。

E标志信息:printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%lu\n", zone_names[j], realsize, batch),注:batch应该为255

F初始化wait_table:zone->wait_table_xx等

G设置zone->zone_start_page = zone_start_pfn

H设置zone->zone_mem_map = pfn_to_page(zone_start_pfn)

I memmap_init(size, nid, j, zone_start_pfn),直接调用函数memmap_init_zone,参数完全

一致

J对于本zone内的每个物理页面,设置其page结构属性参数

a设置page->flags

b设置页面为Reserved

K zonetable_add(zone, nid, j, zone_start_pfn, size):设置全局变量zone_table

L zone_init_free_lists(pgdat, zone, zone->spanned_pages):初始化zone->free_area为空31check_ioapic:处理VIA和Nvidia主板的一些特性

32若开启编译选项ACPI_BOOT则执行acpi_boot_init:处理ACPI_BOOT、ACPI_FADT、MADT (Multiple APIC Description Table)和ACPI_HPET(需要开启编译选项HPET_TIMER)

33get_smp_config:

I若acpi_lapic && acpi_ioapic同时有效则打印特征信息printk(KERN_INFO "Using ACPI (MADT) for SMP configuration information\n")后返回

II否则打印标志信息::printk("Intel MultiProcessor Specification v1.%d\n", mpf->mpf_ specification)

III调用函数smp_read_mpc分析MPC(multiple processor config)表,处理多处理器信息i比较MPC表签名是否为“PCMP”、校验和、版本号以及LAPIC是否存在

ii打印标志信息:printk(KERN_INFO "OEM ID: %s ",str)

iii打印标志信息:printk(KERN_INFO "Product ID: %s ",str)

iv打印标志信息:printk(KERN_INFO "APIC at: 0x%X\n",mpc->mpc_lapic)

v分析MPC表

vi如果是处理器表项,则调用函数MP_processor_info:

A打印标志信息:printk(KERN_INFO "Processor #%d %d:%d APIC version %d\n",...);

B增加总CPU计数num_processors,

C physid_set(m->mpc_apicid, phys_cpu_present_map):设置当前CPU物理APIC_ID到

全局变量phys_cpu_present_map中

D设置bios_cpu_apicid[cpu]=x86_cpu_to_apicid[cpu]=m->mpc_apicid,其中BP时cpu=0,AP时为CPU在MPC表中的序号

E在全局变量cpu_possible_map和cpu_present_map表中标志当前CPU有效vii如果是总线表项,则调用函数MP_bus_info:

viii如果是IOAPIC表项,则调用函数MP_ioapic_info:

打印标志信息:printk("I/O APIC #%d Version %d at 0x%X.\n",...);

ix如果是中断源表项,则调用函数MP_intsrc_info:

x如果是本地中断源表项,则调用函数MP_lintsrc_info:

34init_apic_mappings

I建立FIX_APIC_BASE固定映射

II建立FIX_IO_APIC_BASE_0等IOAPIC的固定映射

35probe_roms:记录ROM占用的资源(地址空间信息等)

36e820_reserve_resources:根据e820信息,保留e820内存在iomem_resource(资源空间初值为全部地址空间)中占用的空间,以及核心映像代码段、数据段在每个e820资源空间的占用情况

37保留视频RAM占用的iomem_resource资源空间

38保留标准I/O设备占用ioport_resource资源空间

39如果开启编译选项GART_IOMMU,则调用函数iommu_hole_init

40若开启编译选项VGA_CONSOLE(通常都开启)则设置conswitchp = &vga_con,否则若开启DUMMY_CONSOLE,则设置conswitchp = &dummy_con

41setup_arch结束

1.3.3后期体系结构无关初始化start_kernel

42setup_per_cpu_areas:对于系统中的每个CPU,调用函数alloc_bootmem分配一个存储区ptr,将数据区[__per_cpu_start,__per_cpu_end]的内容复制到存储区ptr,并设置cpu_pda[cpu].data_offset = ptr - __per_cpu_start

43smp_prepare_boot_cpu:在全局变量cpu_online_map、cpu_callout_map、cpu_sibling_map[0]和cpu_core_map中标记本处理器有效

44sched_init:初始化各CPU的进程运行队列runqueue,增加init_mm.mm_count引用计数,为本CPU 初始化idle进程

45build_all_zonelists:对于系统中的每个NODE i调用函数build_zonelists(NODE_DATA(i)/pgdat):I初始化pgdat->node_zonelists[*].zone[0] = NULL

II根据本NODE与系统中的全部NODE之间的距离,由近及远遍历系统系统中的全部NODE,调用函数build_zonelists_node将pgdat->zone_zonelist[*].zone[i]指向每个NODE中对应类型的

zone区

注:

在pgdat(类型struct pglist_data{})中定义:

struct zone node_zones[MAX_NR_ZONES]; // MAX_NR_ZONES = 3

struct zonelist node_zonelists[GFP_ZONETYPES] // GFP_ZONETYPES = 3 而zonelist的定义为:

struct zonelist {

truct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1];

};

即全部为指向zone的指针,共计为系统中的NODE数目乘以每个NODE中的zone数目,另外一个为NULL的结束指针,即zonelist中的指针可以指向系统中的全部NODE中的

每个zone。

上述初始化过程就是将每个NODE的zonelists中的每个指针指向系统中全部NODE中的zone,并按NODE距离升序排序,距离最近的排在前面。

III显示特征信息:printk("Built %i zonelists\n", num_online_nodes())

IV cpuset_init_current_mems_allowed:设置current->mems_allowed = NODE_MASK_ALL

46age_alloc_init,直接调用宏hotcpu_notifier(page_alloc_cpu_notify, 0),定义一个静态变量通知块struct nodifier_block page_alloc_cpu_notify_nb = { page_alloc_cpu_notify, 0},并调用函数register_cpu_notifier注册通知块,将通知块page_alloc_cpu_notify_nb注册到全局CPU活动通知链cpu_chain中

47显示特征信息:printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line)

48parse_early_param:分析早期启动参数

49parse_args:分析命令行参数:("Booting kernel", command_line, __start___param, __stop___param - __start___param, &unknown_bootoption);

50sort_main_extable:直接调用函数sort_extable(__start___ex_table, __stop___ex_table),进而调用sort 函数,对异常表中的内容进行快速排序

51trap_init:异常初始化

I初始化异常处理向量(小于32的中断向量)

II cpu_init():初始化CPU

i若不是CPU 0,则调用函数pda_init(cpu)设置CPU基本信息

ii并显示特征信息:printk("Initializing CPU#%d\n", cpu)

iii设置GDT和IDT

iv syscall_init():系统调用入口初始化

A wrmsrl(MSR_STAR, ((u64)__USER32_CS)<<48 | ((u64)__KERNEL_CS)<<32):设

置x86遗留模式(legacy x86 mode)系统调用入口地址

B wrmsrl(MSR_LSTAR, system_call):设置长模式(long mode)下64位软件入口地址

C syscall32_cpu_init(),需要开启编译选项IA32_EMULATION,设置长模式下的兼容软

件系统调用入口地址

注释:x86-64中使用syscall/sysret指令进入/返回系统调用,入口地址使用STAR(C000_0081h)、

LSTAR(C000_0082h)和CSTAR(C000_0083h)模式寄存器。对应汇编指令入口分别是

ia32_syscall、ia32_cstar_target和system_call。系统调用表位于文件arch/x86_64/ia32/ia32entry.S

和include/asm-x86_64/unistd.h中。不再使用80h软中断,但开启编译选项IA32_EMULATION

后80h中断仍可用。

III fpu_init():初始化浮点处理器

52rcu_init:rcu初始化,

I调用函数rcu_cpu_notify;

II调用函数register_cpu_notifier注册rcu通知块rcu_nb到cpu_chain链表上,其中rcu_nb的回调函数即是rcu_cpu_notify;

53init_IRQ:

I init_ISA_irqs:

i调用init_bsp_APIC:若SMP模式或者CPU已有APIC直接返回,否则设置本地APIC

ii调用函数init_8259A(0):初始化8259

iii初始化中断描述结构irq_desc[224]为空状态,对于前16个中断则使用8259A类型处理

iv设置中断向量[32..255]到中断门中

II对于SMP配置,设置处理器间中断和APIC中断

III调用函数setup_timer:访问I/O端口0x43、0x40初始化定时器

IV如果acpi_ioapic为0,则初始化中断请求号2(中断向量34),中断处理程序为irq2

54pidhash_init:PID哈希表初始化

55init_timers:定时器初始化

I调用函数timer_cpu_notify;

II调用函数register_cpu_notifier注册定时器通知块timers_nb到cpu_chain链表上,其中timers_nb 的回调函数即是timer_cpu_notify。

III open_softirq(TIMER_SOFTIRQ, run_timer_softirq, NULL):初始化定时器软中断(TIMER_SOFTIRQ):softirq_vec [ TIMER_SOFTIRQ ].data = NULL; softirq_vec

[ TIMER_SOFTIRQ].action = run_timer_softirq;

56softirq_init:调用open_softirq初始化其它软中断:

I open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);

II open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);

57time_init:系统时间初始化,设置全局变量xtime、wall_to_monotonic、vxtime_hz、cpu_khz等

I get_cmos_time:获得CMOS时间,设置到https://www.sodocs.net/doc/0517577038.html,_sec中

II set_normalized_timespec:设置wall_to_monotonic

III hpet_init:初始化HPET

i设置固定映射FIX_HPET_BASE和VSYSCALL_HPET

ii通过接口hpet_readl获取HPET信息,若存在HPET则继续执行

iii hpet_timer_stop_set_go:初始化HPET

IV若HPET存在,即hpet_use_timer置1,则置cpu_khz=hpet_calibrate_tsc(),设置时钟的名称timename为“HPET”,否则向下执行

V若开启编译选项X86_PM_TIMER且初始化ACPI过程中将pmtmr_ioport置1,则执行pit_init,并置cpu_khz = pit_calibrate_tsc(),设置时钟的名称timename为“PM”,否则向下执行VI pit_init,并置cpu_khz = pit_calibrate_tsc(),设置时钟的名称timename为“PIT”

VII标志信息:printk(KERN_INFO "time.c: Using %ld.%06ld MHz %s timer.\n", vxtime_hz / 1000000, vxtime_hz % 1000000, timename); 这里给出使用时钟的名称timename VIII printk(KERN_INFO "time.c: Detected %d.%03d MHz processor.\n", cpu_khz / 1000, cpu_khz % 1000);

IX rdtscll_sync(&https://www.sodocs.net/doc/0517577038.html,st_tsc):将https://www.sodocs.net/doc/0517577038.html,st_tsc设置为当前TSC值

X setup_irq(0, &irq0);设置定时器中断处理程序为irq0

XI set_cyc2ns_scale(cpu_khz / 1000)

XII time_init_gtod:需要开启SMP选项,“Decide after all CPUs are booted what mode gettimeofday should use”

i unsynchronized_tsc()

ii标志信息:printk(KERN_INFO "time.c: Using %s based timekeeping.\n", timetype)

58console_init:早期控制台初始化

I tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY):设置tty_ldiscs[N_TTY],Setup the default TTY

line discipline

II disable_early_printk,若开启编译选项EARL Y_PRINTK,关闭早期打印输出

III执行函数指针[__con_initcall_start,__con_initcall_end],即执行console_initcall定义的各函数59profile_init:

若prof_on = 0 则直接返回,否则继续执行,prof_on由启动参数profile=xx开启,参见本节注释prof_len = (_etext - _stext) >> prof_shift

pro_buffer = alloc_bootmem(prof_len*sizeof(atomic_t))

注:若命令行参数包含“profile=schedule,n”或“profile=n”(n为数字),则执行profile_setup

函数设置profile功能:

i若“profile=schedule,n”则

设置prof_on = SCHED_PROFILING(等于2)

prof_shift = n

标志信息:printk(KERN_INFO"kernel schedule profiling enabled (shift: %ld)\n", prof_shift);

ii若“profile= n”则

设置prof_on = CPU_PROFILING(等于1)

prof_shift = n

标志信息:printk(KERN_INFO "kernel profiling enabled (shift: %ld)\n",prof_shift)

60local_irq_enable:开启中断

61vfs_caches_init_early:

I dcache_init_early:dentry哈希表初始化:调用函数dentry_hashtable = alloc_large_system

("Dentry cache", ...)分配dentry哈希表,并初始化表头全部为空。在函数alloc_large_system中

显示标志信息:printk("%s hash table entries: %d (order: %d, %lu bytes)\n", tablename, (1U <<

log2qty), long_log2(size) - PAGE_SHIFT,size);

II inode_init_early:inode哈希表初始化:调用函数inode_hashtable =alloc_large_system_hash ("Inode-cache", ...)分配哈希表,并初始化表头全部为空。在函数alloc_large_system中显示标

志信息:printk("%s hash table entries: %d (order: %d, %lu bytes)\n", tablename, (1U << log2qty),

long_log2(size) - PAGE_SHIFT,size);

62mem_init:内存页面管理机制初始化

I设置全局变量max_low_pfn = max_pfn = num_physpages = end_pfn;

II high_memory = (void *) __va(end_pfn * PAGE_SIZE)

III memset(empty_zero_page, 0, PAGE_SIZE):保留的0页面empty_zero_page清0

IV开启NUMA编译选项,则执行totalram_pages += numa_free_all_bootmem(),函数numa_free_all_bootmem根据标记node_online_map,对每个在线有效NODE i调用函数

free_all_bootmem_node(NODE_DATA(i)),进而直接调用函数free_all_bootmem_core(pgdat)建

立本NODE页面表和Buddy内存管理机制:

i本NODE起始物理页面号:pfn = bdata->node_boot_start(bdata = pgdat->bdata)

ii本NODE页面数目bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT)

iii本NODE位图基地址map = bdata->node_bootmem_map

iv如果pfn等于0,或64(long类型位图)页面对齐则标记gofast=1,一次可以处理连续的64个页面。

v__ClearPageReserved:清除当前处理页面的Reserved标记

vi__free_pages(page, order),order根据连续的页面数目确定。

A free_hotpage(page):如果order等于0。进而直接调用free_hot_cold_page(page, 0)

a zone = page_zone(page):获取页面page所在的zone指针

b free_pages_check:测试page中的标志位,页面是否可以安全释放,如果PG_dirty

标记有效,则调用宏ClearPageDirty(page)直接清除“脏”标记。

c per_cpu_pages * pcp = zone-> pageset[get_cpu()]->pcp[0],即获得本页面所属zone、

当前处理器对应的的单页面链表管理结构pcp

d使用page->lru指针将页面page加入到pcp->list页面中,并增加pcp计数。如果pcp中页面数量超过上限pcp->high,则调用函数free_pages_bulk(zone, pcp->batch,

&pcp->list, 0)释放pcp->batch个页面。

e free_pages_bulk:当队列pcp->list不空且处理的页面不超过pcp->batch时,调用

函数__free_page_bulk,每次释放一个页面。__free_page_bulk:

(1)destroy_compound_page(struct page* page, unsigned long order):当开启编译

选项HUGETLB_PAGE且order > 0时调用

(I)如果标记PG_compound未置位,不是一个复合页面则直接返回。

(II)if ( page[1].index != order) 则出错,即大页面中的第二个4KB子页面对

应的page结构中index成员用于记录页面大小

(III)ClearPageCompound:清除大页面的全部子页面的复合页面标记,同时

判断各页面page的private成员是否指向第一个页面的page结构

(IV)page_idx = page_to_pfn(page) & (( 1 << MAX_ORDER ) – 1 ),当前页面

在BUDDY范围内序号

(V)while ( ordef < MAX_ORDER – 1) {...},整个循环实现buddy算法的内

存回收过程

1combined_idx = __find_combined_index ( page_idx, order ) =

( page_idx & ~(1 << order)),结合后的第一个页面序号,page_idx

是2order对齐的页面号。除非order=0,page_idx为任意值,否则

是一个BUDDY的首页面

2buddy_idx = page_idx ^ (1 << order)

3bad_range(zone, buddy):判断buddy_idx是否在正常返回内,且

属于本zone等,非法则推出循环

4page_is_buddy(buddy, order):buddy是否有效,非法则推出循环

5(zone->free_area+order)->nr_free--;rmv_page_order(buddy);buddy

已结合成更大的页面,从当前order中删除

6page_idx = combined_idx;order++;进行下一个循环处理

(VI)set_page_order(page, order):page->private = order;设置page->flags中

的PG_private标志有效

(VII)list_add(&page->lru, &zone->free_area[order].free_list):将page结构加

入到相应空闲链表中

B__free_pages_ok(page, order),order不等于0时调用

A LIST_HEAD(list):定义一个临时表表头

B mod_page_state(pgfree, 1 << order):page_states.pgfree += 1 << order,结构变量

page_states,每个CPU定义一个独立分量

C free_pages_check:核对每个页面的有效性

D list_add(&page->lru, &list):将第一个页面加入到临时表头中,该链表只有这一

个分量

E free_pages_bulk(page_zone(page), 1, &list, order):释放页面,同前面说明、

63kmem_cache_init:kmem_cache机制初始化

I初始化链表cache_chain

II初始化cache_cache,将cache_cache加入到cache_chain中,每次调用函数kmem_cache_create 创建一个kmem_cache后,都将使用cache.next域加入链表cache_chain中III初始化malloc_sizes,为各malloc_sizes[*]中成员cs_cachep和cs_dmacachep分别调用函数kmem_cache_create,建立内存空间

IV设置cache_cache.array[smp_processor_id()]和malloc_sizes[0].cs_cachep->array[smp_processor _id()]

V register_cpu_notifier(&cpucache_notifier)

64setup_per_cpu_pageset:

I process_zones(smp_processor_id()):为CPU 0设置per_cpu_pageset

i对系统中的每个zone,设置zone->pageset[cpu]= kmalloc_node(...);kmalloc_node(size_t size,

unsigned int __nocast flags, int node):功能与kmalloc相同,也是在malloc_size[*]中分配

指定容量内存,不同的是优先分配指定节点(NODE)node的内存,节点node没有内存

再从其它节点分配

ii setup_pageset:设置zone->pageset[cpu]->pcp[*]

II register_cpu_notifier(&pageset_notifier):注册CPU启动通知链pageset_notifier

65numa_policy_init:

I初始化专用缓存:policy_cache = kmem_cache_create ("numa_policy", sizeof(struct mempolicy), ...)

II初始化专用缓存:sn_cache = kmem_cache_create("shared_policy_node", sizeof(struct sp_node), ... )

III sys_set_mempolicy(MPOL_ INTERLEA VE, ...):设置内存为交存(MPOL_INTERLEA VE)策略,以使启动过程中分配的内存不全在NODE 0中;

66calibrate_delay:标定时钟,设置计算能力标称值BogoMIPS,设置全局变量loops_per_jiffy I若使用了命令行参数“lpj=xxx”,直接置loops_per_jiffy = preset_lpj,并显示标志信息:printk("Calibrating delay loop (skipped)... %lu.%02lu BogoMIPS preset\n", loops_per_jiffy/

(500000/HZ), (loops_per_jiffy/(5000/HZ)) % 100);...);

II loops_per_jiffy = calibrate_delay_direct():若返回值不为0,则显示标志信息:printk("Calibrating delay using timer specific routine.."); printk("%lu.%02lu BogoMIPS (lpj=%lu)\n", loops_per_jiffy/

(500000/HZ), (loops_per_jiffy/(5000/HZ)) % 100, loops_per_jiffy);

III开始执行标定代码,计算loops_per_jiffy,显示标志信息:printk(KERN_DEBUG "Calibrating delay loop... "); printk("%lu.%02lu BogoMIPS (lpj=%lu)\n", 同上);

注:命令行参数“lpj=xxx”修改全局变量preset_lpj,在此起作用

67pidmap_init:设置全局变量pidmap_array[0],为pidmap_array[0].page分配一个内存页面,调用函数attach_pid(current,...)标志进程0 PID已经使用

68pgtable_cache_init:空函数

69prio_tree_init:基数优先级搜索树(radix priority search tree)初始化,初始化静态变量index_bits_to_maxindex

70anon_vma_init:初始化专用缓存:anon_vma_cachep = kmem_cache_create ("anon_vma", sizeof(struct anon_vma), ...)

71如果efi_enabled则调用函数efi_enter_virtual_mode,需要编译选项EFI

72fork_init(num_physpages):进程管理初始化

I初始化专用缓存:task_struct_cachep = kmem_cache_create("task_struct", sizeof(struct task_struct), ... );

II设置max_threads:max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE)

III设置init_task.signal->rlim[RLIMIT_NPROC]等

73proc_caches_init:调用函数kmem_cache_create初始化进程相关cache

I sighand_cachep = kmem_cache_create("sighand_cache", sizeof(struct sighand_struct), ...);

II signal_cachep = kmem_cache_create("signal_cache", s izeof(struct signal_struct), ...);

III files_cachep = kmem_cache_create("files_cache", sizeof(struct files_struct), ...);

IV fs_cachep = kmem_cache_create("fs_cache", sizeof(struct fs_struct), ...);

V vm_area_cachep = kmem_cache_create("vm_area_struct", sizeof(struct vm_area_struct), ...);

VI mm_cachep = kmem_cache_create("mm_struct", sizeof(struct mm_struct), ...);

74buffer_init:

I初始化专用缓存:bh_cachep = kmem_cache_create("buffer_head", sizeof(struct buffer_head), ... );

II设置静态变量max_buffer_heads

III hotcpu_notifier(buffer_cpu_notify, 0):注册CPU启动通知链buffer_cpu_notify

75unnamed_dev_init:仅调用函数idr_init(&unnamed_dev_idr):

I init_id_cache():初始化专用缓存:idr_layer_cache = kmem_cache_create("idr_layer_cache",

sizeof(struct idr_layer), ...);

II unnamed_dev_idr空间清0,初始化自旋锁unnamed_dev_idr.lock;

76key_init:初始化密钥(key)管理

I初始化专用缓存:key_jar = kmem_cache_create("key_jar", sizeof(struct key)

II将key_type_keyring.link、key_type_dead.link和key_type_user.link加入到链表key_types_list 尾部

III各相关全局变量初始化

77security_init:安全机制初始化

I显示标志信息:printk(KERN_INFO "Security Framework v" SECURITY_FRAMEWORK_ VERSION " initialized\n");

II verify(&dummy_security_ops),调用函数security_fixup_ops(&dummy_security_ops),对dummy_security_ops中的每个成员调用宏set_to_dummy_if_null,设置dummy_security_ops

各成员初值指向对应的空函数dummy_xx函数(security/dummy.c中)

III初始化指针security_ops = &dummy_security_ops

IV do_security_initcalls:调用[__security_initcall_start,__security_initcall_end]中的每个指针函数,进行安全机制初始化,即调用由宏security_initcall(fn)初始化的函数集合。目前源代码中仅有

函数security/capability.c/capability_init()、security/selinux/hooks.c/selinux_init()和security/

root_plug.c/rootplug_init()

78vfs_caches_init(num_physpages):文件系统VFS层初始化

I计算保留内存reserve = min((num_physpages - nr_free_pages()) * 3/2, mempages - 1),即保留已使用内存页面的1.5倍,以剩余内存mempages = num_physpages – reserve作为以下初始化时

使用的内存容量;

II专用缓存初始化:names_cachep = kmem_cache_create("names_cache", PATH_MAX, ...)

III专用缓存初始化:filp_cachep = kmem_cache_create("filp", sizeof(struct file), ...);

IV dcache_init(mempages):

i专用缓存初始化:dentry_cache = kmem_cache_create("dentry_cache", sizeof(struct dentry), ...)

ii set_shrinker(DEFAULT_SEEKS, shrink_dcache_memory):分配一个struct shrinker结构shrinker,设置shrinker->shrinker = shrink_dcache_memory,并将shrinker->list加入到全局

链表shrinker_list中

iii dcache_init_early()中已经初始化了dentry_hashtable,此处不再初始化,为什么要早期初始化?

V inode_init(mempages):

i专用缓存初始化:inode_cachep = kmem_cache_create("inode_cache", sizeof(struct inode), ...);

ii set_shrinker(DEFAULT_SEEKS, shrink_icache_memory):作用同前一步

iii inode_init_early()中已经初始化inode_hashtable,此处不再初始化

VI files_init(mempages):设置全局变量files_stat中成员max_files初值,根据剩余内存容量计算VII mnt_init(mempages):

i专用缓存初始化:mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),...);

ii为mount_hashtable分配一个页面,并初始化为哈希表表头

iii sysfs_init():sysfs文件系统初始化,需要开启编译选项SYSFS

A专用缓存初始化:sysfs_dir_cachep = kmem_cache_create("sysfs_dir_cache",

sizeof(struct sysfs_dirent), ...);

B register_filesystem(&sysfs_fs_type):注册文件系统sysfs_fs_type

C sysfs_mount = kern_mount(&sysfs_fs_type):安装内部文件系统sysfs,直接调用函数

do_kern_mount(type->name, 0, type->name, NULL),后续工作参见文件系统安装部分iv init_rootfs():直接调用函数register_filesystem(&rootfs_fs_type),注册文件系统rootfs_fs_type

v init_mount_tree():初始化文件系统安装树

A mnt = do_kern_mount("rootfs", 0, "rootfs", NULL):安装内部文件系统rootfs

B分配一个struct namespace结构namespace并初始化

C list_add(&mnt->mnt_list, &namespace->list):将rootfs安装点加入namespace成员链表

D namespace->root = mnt,mnt->mnt_namespace = namespace:rootfs安装点作为根目录

E设置init_https://www.sodocs.net/doc/0517577038.html,space = namespace

F对系统中的当前每个线程p,设置p->namespace = namespace,p的类型为task_struct

G set_fs_pwd:设置当前进程当前目录和安装点分别为namespace->root和namespace->

root->mnt_root

H set_fs_root:设置当前进程根目录和安装点分别为namespace->root和namespace->

root->mnt_root

VIII bdev_cache_init():块设备初始化

i专用缓存初始化:bdev_cachep = kmem_cache_create("bdev_cache", sizeof(struct bdev_inode), ...);

ii register_filesystem(&bd_type):注册块设备文件系统bd_type

iii bd_mnt = kern_mount(&bd_type):内部安装块设备文件系统

iv blockdev_superblock = bd_mnt->mnt_sb:设置块设备超级块

IX chrdev_init():直接调用cdev_map = kobj_map_init(base_probe, &chrdevs_lock)

79radix_tree_init:基数树初始化

I专用缓存初始化:radix_tree_node_cachep = kmem_cache_create("radix_tree_node", sizeof(struct radix_tree_node), ...);

II radix_tree_init_maxindex():初始化静态变量height_to_maxindex[*]各分量

III hotcpu_notifier(radix_tree_callback, 0):注册CPU启动通知链

80signals_init():信号初始化,仅初始化专用缓存sigqueue_cachep =kmem_cache_create("sigqueue", sizeof(struct sigqueue), ...);

81page_writeback_init():初始化页面回写机制

I根据内存容量设置相关全局变量dirty_background_ratio和vm_dirty_ratio

II mod_timer(&wb_timer, ...):修改wb_timer超时时间

III set_ratelimit():设置限定参数ratelimit_pages

IV register_cpu_notifier(&ratelimit_nb):注册CPU启动链ratelimit_nb

82proc_root_init:proc文件系统初始化

I proc_init_inodecache():初始化专用缓存proc_inode_cachep = kmem_cache_create("proc_inode_

cache", sizeof(struct proc_inode), ...);

II register_filesystem(&proc_fs_type):注册proc文件系统

III proc_mnt = kern_mount(&proc_fs_type):内部安装proc文件系统

IV proc_misc_init():建立/proc目录下各常规文件,通常为只读属性

V在目录/proc下建立子目录:net、net/stat、sysvipc(需开启编译选项SYSVIPI)、sys(需开启编译选项SYSCTL)、fs、dirver、fs/nfsd、bus等

VI proc_tty_init:初始化子目录/proc/tty

83cpuset_init():需要开启编译选项CPUSETS,初始化CPU工作集

I初始化全局变量top_cpuset,设置init_task.cpuset = top_cpuset

II register_filesystem(&cpuset_fs_type):注册CPU工作集文件系统cpuset

III cpuset_mount = kern_mount(&cpuset_fs_type):内部安装CPU工作集文件系统

IV设置top_cpuset.dentry = cpuset_mount->mnt_sb->s_root,及相关成员

V cpuset_populate_dir(cpuset_mount->mnt_sb->s_root):多次调用函数cpuset_add_file向该dentry 中增加相关文件

84check_bugs():体系结构相关功能进一步初始化

I identify_cpu(c = &boot_cpu_data):CPU相关参数进一步初始化

i early_identify_cpu(c):CPU初步识别,第一次设置phys_proc_id[smp_processor_id()]为

APIC_ID

ii cpuid_eax():进一步识别CPU

iii init_amd(c):若是AMD处理器

A get_model_name(c):设置CPU类型记录boot_cpu_data.x86_model_id值

B display_cacheinfo(c):显示CPU Cache信息

C设置当前CPU核数到c->x86_num_cores中

D amd_detect_cmp(c):检测CPU多核配置

a cpu_core_id[cpu] = phys_proc_id[cpu] & ((1 << bits)-1):设置当前CPU在本封装内

的CPU核ID

b phys_proc_id[cpu] >>= bits:设置当前CPU本封装ID(APIC_ID去掉CPU核编

号)

c若acpi_numa <= 0则设置cpu_to_node[cpu] = phys_proc_id[cpu]

d显示标志信息:printk(KERN_INFO "CPU %d(%d) -> Node %d -> Core %d\n", …);

iv init_intel(c):若是INTEL处理器

v display_cacheinfo(c):若是其它公司的X86处理器

vi select_idle_routine(c):如果CPU支持Monitor/Mwait support特性且pm_idle为空,设置pm_idle = mwait_idle

注:命令行参数idle="poll"将pm_idle = poll_idle

vii detect_ht(c):设置Hyper-Threading功能,需要开启编译选项SMP

A cpuid(1,...):获得每个CPU封装内的sibling数,可能是以超线程CPU计算

B设置全局变量phys_proc_id[smp_processor_id()] = phys_pkg_id(index_msb);

C显示标志信息:printk(KERN_INFO "CPU: Physical Processor ID: %d\n", phys_proc_id[cpu]);

D设置全局变量cpu_core_id[smp_processor_id()]

viii mcheck_init(c):需要开启编译选项X86_MCE,

A mce_init:MCE(Machine Check Exception)功能初始化,调用函数do_machine_check

等,并访问CPU内部相关寄存器

B mce_cpu_features:如果是INTEL处理器,则调用函数mce_intel_feature_init()→

intel_init_thermal()进一步初始化

II如果没有开启编译选项SMP,显示CPU特征标志信息:printk("CPU: ");...

III alternative_instructions():调用函数apply_alternatives(__alt_instructions, __alt_instructions_end),用CPU结构相关的快速指令替换区间[__alt_instructions, __alt_instructions_end]中原指令,链

接脚本指出替换区位置。若命令行参数包含noreplacement,则不执行上述替换功能。

85acpi_early_init:ACPI早期初始化,

86rest_init():准备执行内核的下一步初始化功能

I kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND):创建init内核线程

II numa_default_policy():调用函数sys_set_mempolicy(MPOL_DEFAULT, ...)设置NUMA结构内

存分配策略为默认值MPOL_DEFAULT

III unlock_kernel():内核解锁

IV preempt_enable_no_resched():宏,barrier(); dec_preempt_count():内核进入抢占式调度状态V schedule():进行一次进程调度

VI cpu_idle():进程0开始执行idle进程

1.4init进程

87lock_kernel():内核加锁

88set_cpus_allowed(current, CPU_MASK_ALL):设置init进程允许在全部CPU上运行

I rq = task_rq_lock(p=current, &flags):获取当前进程(init)所在的运行队列rq

II cpus_intersects(new_mask=CPU_MASK_ALL, cpu_online_map):测试新CPU掩码字与在线CPU(cpu_online_map)掩码字是否为空,为空则出错

III p->cpus_allowed = new_mask:设置进程新的CPU掩码字

IV cpu_isset(task_cpu(p), new_mask):测试进程init当前运行的CPU是否在新的掩码字中,是则成功推出

V migrate_task(p, any_online_cpu(new_mask), req):进程init当前运行的CPU不在新的掩码字中,迁移进程init到新的CPU掩码字中的任何一个在线CPU,

i若init不在运行队列中(p->array= NULL && task_running(rq, p) == NULL)

A调用函数set_task_cpu()设置p->thread_info->cpu = new_mask,

ii若init在运行队列中,填写迁移命令req,

A将req->list加入rq的迁移命令能够队列rq->migration_ queue中

B wake_up_process(rq->migration_thread):唤醒迁移线程

C wait_for_completion(&req.done):等待迁移结束

D tlb_migrate_finish(p->mm):迁移结束,刷新TLB

89child_reaper = current

90smp_prepare_cpus(max_cpus):准备启动SMP中的其它CPU,参数maxcpus缺省值为NR_CPUS,启动参数“maxcpus=xx”将重新设置参数maxcpus值为xx:

I nmi_watchdog_default():设置全局变量nmi_watchdog,如果当前不是缺省值NMI_DEFAULT,

则直接返回(命令行参数nmi_watchdog=xxx将设置nmi_watchdog值),否则如果是INTEL

或AMD CPU且类型(boot_cpu_data.x86)参数为15,则设置nmi_watchdog =

NMI_LOCAL_APIC,否则设置nmi_watchdog = NMI_IO_APIC

II current_cpu_data = boot_cpu_data:设置当前CPU特征参数,开启编译选项SMP时宏current_cpu_data定义为cpu_data [smp_processor_id()]

III current_thread_info()->cpu = 0

IV enforce_max_cpus(max_cpus):清除大于max_cpus的CPU在全局变量cpu_possible_map和cpu_present_map中的标记,使大于max_cpus的CPU号不可用

V prefill_possible_map():需要开启编译选项HOT_PLUG_CPU,将系统支持的NR_CPUS个CPU 全部加入到cpu_possible_map中

VI smp_sanity_check(max_cpus):验证SMP配置可行性,若失败则关闭SMP功能

i测试当前BP是否在全局变量phys_cpu_present_map中已经标记,未标记则再次标记

ii若smp_found_config为0,则SMP配置失败,直接返回

iii boot_cpu_id是否已经在全局变量phys_cpu_present_map中已经标记,未标记则再次标记

iv是否存在本APIC

VII connect_bsp_APIC():若当前已是APIC模式则无动作,否则由当前PIC模式切换到APIC模式:调用函数clear_local_APIC()复位本地APIC,通过端口22h和23h写数据VIII setup_local_APIC():本地APIC初始化

IX setup_IO_APIC():启动I/O APIC:

i enable_IO_APIC():

A初始化全局变量irq_2_pin[*]

B如果没有命令行参数设置pirq=xx,则初始化全局变量pirq_entries[*]

C根据全局变量nr_ioapics值访问各IOAPIC,设置各IOAPIC中的中断引脚数目全局变量nr_ioapic_registers[*],全局变量nr_ioapics值由函数mp_register_ioapic()和

MP_ioapic_info()设置

D clear_IO_APIC():对全部IOAPIC的各引脚调用函数clear_IO_APIC_pin()清除中断

ii如果ACPI分析函数acpi_process_madt()已经设置了IOAPIC(acpi_ioapic=1),则设置io_apic_irqs = ~0,即全部IRQ都通过IOAPIC,否则设置io_apic_irqs = ~ PIC_IRQS iii如果ACPI分析函数acpi_process_madt()没有设置了IOAPIC(acpi_ioapic=0),调用函数setup_ioapic_ids_from_mpc从MPC表中分析IOAPIC,设置全部IOAPIC相关寄存器,显

示标志信息:printk(KERN_INFO "Using IO-APIC %d\n", mp_ioapics[apic].mpc_apicid) iv sync_Arb_IDs()

v setup_IO_APIC_irqs():设置IOAPIC各引脚中断向量,

注1:命令行参数"apic=debug"或"apic=verbose"可以是内核启动过程中打印APIC相关信

息,其中debug时打印更多

注2:中断引脚数目参见结构union IO_APIC_reg_01{}定义,IOAPIC其它寄存器参见系

列结构IO_APIC_reg_xx{}定义

注3:IOAPIC中断引脚寄存器不能有空行,参见函数setup_IO_APIC_irqs() vi init_IO_APIC_traps():初始化IOAPIC中断向量入口,对于小于16的中断向量,调用函数make_8259A_irq,否则设置irq_desc[*].handler=no_irq_type

vii check_timer():验证定时器,代码比较复杂

viii print_IO_APIC():如果如果ACPI分析函数acpi_process_madt()没有设置IOAPIC (acpi_ioapic=0),打印当前IOAPIC设置

X setup_boot_APIC_clock():设置BP中的APIC定时器

i显示标志信息:printk(KERN_INFO "Using local APIC timer interrupts.\n")

ii calibration_result=calibrate_APIC_clock():标定本地APIC时钟,显示标志信息:printk(KERN_INFO "Detected %d.%03d MHz APIC timer.\n",result / 1000 / 1000, result /

1000 % 1000);

iii setup_APIC_timer(calibration_result):启动本地APIC中的定时器

91do_pre_smp_initcalls():

I migration_init():

i migration_call(&migration_notifier, CPU_UP_PREPARE, cpu):

A p = kthread_create(migration_thread, hcpu, "migration/%d",cpu):为当前CPU创建进程

迁移线程,名字为migration n,n为CPU号,这里n=0,线程的入口函数为

migration_thread

B kthread_bind(p, cpu):帮定迁移线程migration n只能运行于当前CPU

C set_task_cpu(p, cpu):p->thread_info->cpu = cpu

D p->cpus_allowed = cpumask_of_cpu(cpu)

E__setscheduler(p, SCHED_FIFO, MAX_RT_PRIO-1):设置调度策略FIFO和调度优先级极高

F cpu_rq(cpu)->migration_thread = p:设定当前CPU的迁移线程为刚创建的线程

ii migration_call(&migration_notifier, CPU_ONLINE, cpu):仅调用函数wake_up_process (cpu_rq(cpu)->migration_thread),唤醒当前CPU的迁移线程

iii register_cpu_notifier(&migration_notifier):调用函数notifier_chain_register(&cpu_chain, nb)

注册通知块migration_notifier到CPU启动/关闭通知链

II spawn_ksoftirqd():

i cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu)

A p = kthread_create(ksoftirqd, hcpu, "ksoftirqd/%d", hotcpu):为当前CPU创建核心线程

ksoftirqd n,n为CPU号,入口函数为ksoftirqd

B kthread_bind(p, hotcpu):帮定线程ksoftirqd n只能运行于当前CPU

C per_cpu(ksoftirqd, hotcpu) = p:标记线程p到CPU管理结构中

ii cpu_callback(&cpu_nfb, CPU_ONLINE, cpu):仅调用函数wake_up_process (per_cpu(ksoftirqd, hotcpu)),唤醒当前CPU的软中断处理线程

iii register_cpu_notifier(&cpu_nfb):注册通知块cpu_nfb到CPU启动/关闭通知链

92fixup_cpu_present_map():如果全局变量cpu_present_map为空,则将cpu_possible_map中记录的每个CPU标记到cpu_present_map中

93smp_init():SMP功能启动其它AP

I cpu_up(i):对于cpu_present_map中记录的每个CPU,如果当前CPU i未启动,且当前已启

动的总CPU数目少于全局变量规定max_cpus个,则调用函数cpu_up(i)启动当前CPU:

i notifier_call_chain(&cpu_chain, CPU_UP_PREPARE, hcpu):执行CPU启动链cpu_chain

中的每个通知块

ii__cpu_up(i):启动第i个CPU

A apicid = cpu_present_to_apicid(i):根据全局变量bios_cpu_apicid[i]的记录获得当前

CPU的物理ID

B per_cpu(cpu_state, i) = CPU_UP_PREPARE:设置CPU为准备启动状态

C do_boot_cpu(i, apicid):启动一个CPU,逻辑序号i,物理ID:apicid

a定义一个空闲线程管理结构c_idle

b定义一个工作队列结构work:执行函数为do_fork_idle

c c_idle.idle = get_idle_for_cpu(i):获取当前CPU的空闲进程控制结构

d如果idle进程已经创建(c_idle.idle ≠ 0),设置堆栈c_idle.idle->thread.rsp,调用函数init_idle初始化idle进程:设置为极低优先级,设置掩码智能运行于当前

CPU,加入到当前CPU的调度队列中

e如果idle进程未创建(c_idle.idle = 0),调用函数schedule_work(&work)利用工作队列创建idle进程或调用函数do_fork_idle(即work.func指向)直接创建idle进

程,并设置刚创建的idle进程到idle进程管理结构:即调用宏set_idle_for_cpu(i,

c_idle.idle)设置idle_thread_array[i] = c_idle.idle,do_fork_idle():调用函数

fork_idle():

(I)task = copy_process(CLONE_VM, 0…);复制当前init进程作为新idle进程

(II)init_idle(task, cpu):设置新创建的idle进程参数

(i)rq = cpu_rq(cpu):获取当前CPU的运行队列指针

(ii)idle->cpus_allowed = cpumask_of_cpu(cpu):设置idle进程仅能在当前CPU

上运行

(iii)set_task_cpu(idle, cpu):设置p->thread_info->cpu = cpu,

(iv)rq->curr = rq->idle = idle:设置当前CPU的运行队列的空闲进程

(III)unhash_process(task):从进程pid哈希表删除新创建的idle进程

f设置CPU i的当前进程为idle进程:cpu_pda[i].pcurrent = c_idle.idle

g start_rip=setup_trampoline():获取跳板代码物理地址SMP_TRAMPOLINE_BASE

(6000h),并将跳板代码[trampoline_data, trampoline_end]复制到跳板区6000h

h init_rsp = c_idle.idle->thread.rsp:修改head.S文件中定义的当前进程堆栈为idle

进程堆栈

i per_cpu(init_tss,cpu).rsp0 = init_rsp:设置TSS结构

j initial_code = start_secondary:修改head.S文件中定义的第一个C代码函数入口为start_secondary

k clear_ti_thread_flag(c_idle.idle->thread_info, TIF_FORK):清除idle进程的TIF_FORK标志

l显示标志信息:printk(KERN_INFO "Booting processor %d/%d APIC 0x%x\n", …); m CMOS_WRITE(0xa, 0xf); *((unsigned short *) phys_to_virt(0x469)) = start_rip >> 4;

*((unsigned short *) phys_to_virt(0x467)) = start_rip & 0xf:CMOS地址0xF写为0xA:指示当前系统状态为“当机复位从40:67h处开始执行”,即执行代码start_rip n wakeup_secondary_via_INIT(apicid, start_rip):通过处理器间中断(IPI)向目标CPU发送启动命令,且目标CPU起始代码地址为start_rip,即跳板代码

o BP循环检测当前AP是否启动成功,成功返回0,否则返回失败代码,以下给出AP的启动过程,从实模式汇编代码trampoline_data开始:

① 当前CS:IP值为0600:0000

② 设置DS为0600,代码段、数据段在一起

③ movl $0xA5A5A5A5, trampoline_data - r_base:将跳板代码最初位置写为标

记值A5A5_A5A5h,以便通知运行

④ 设置idt/gdt

⑤ %ax = 1,lmsw %ax:进入保护模式

⑥ ljmpl $__KERNEL32_CS, $(startup_32-__START_KERNEL_map):跳转到文

件head.S中startup_32处开始执行

⑦ 设置CR3、CR4等,进入长模式

⑧ 从startup_64开始执行64位代码,设置CR3,使用页表init_level4_pgt

⑨ 设置堆栈为init_rsp,即前面步骤h设置的idle进程堆栈

⑩ 跳转到C代码initial_code处执行,即前面步骤j设置的函数start_secondary()处执行

?cpu_init():CPU初始化,调用函数pda_init(cpu),初始化本AP的pda结构,设置pda->cpunumber = cpu,以后可以使用函数smp_processor_id()获取CPU

号,并显示标志信息:printk("Initializing CPU#%d\n", cpu)

?smp_callin():向BP报告本AP已经开始运行

(I)cpuid = smp_processor_id():获取当前CPU逻辑号

(II)setup_local_APIC():设置当前AP本地APIC

(III)calibrate_delay():标定当前AP性能bogmips

(IV)disable_APIC_timer():关闭当前APIC定时器

(V)smp_store_cpu_info(cpuid):存储当前AP信息

(i)复制boot_cpu_data结构到cpu_data[cpuid]中

(ii)identify_cpu(cpu_data+cpuid):

(VI)cpu_set(cpuid, cpu_callin_map):将当前CPU ID标记到全局变量cpu_callin_map中

?setup_secondary_APIC_clock():调用函数setup_APIC_timer(calibration_result)设置当前AP的本地APIC定时器

?如果nmi_watchdog使用NMI_IO_APIC,则使用LVT0作为NMI使用

?enable_APIC_timer():本地APIC定时器使能

?set_cpu_sibling_map(smp_processor_id()):

?tsc_sync_wait():调用函数sync_tsc(0)同步TSC

?cpu_set(smp_processor_id(), cpu_online_map):设置当前CPU到全局变量

Linux操作系统源代码详细分析

linux源代码分析:Linux操作系统源代码详细分析 疯狂代码 https://www.sodocs.net/doc/0517577038.html,/ ?:http:/https://www.sodocs.net/doc/0517577038.html,/Linux/Article28378.html 内容介绍: Linux 拥有现代操作系统所有功能如真正抢先式多任务处理、支持多用户内存保护虚拟内存支持SMP、UP符合POSIX标准联网、图形用户接口和桌面环境具有快速性、稳定性等特点本书通过分析Linux内核源代码充分揭示了Linux作为操作系统内核是如何完成保证系统正常运行、协调多个并发进程、管理内存等工作现实中能让人自由获取系统源代码并不多通过本书学习将大大有助于读者编写自己新 第部分 Linux 内核源代码 arch/i386/kernel/entry.S 2 arch/i386/kernel/init_task.c 8 arch/i386/kernel/irq.c 8 arch/i386/kernel/irq.h 19 arch/i386/kernel/process.c 22 arch/i386/kernel/signal.c 30 arch/i386/kernel/smp.c 38 arch/i386/kernel/time.c 58 arch/i386/kernel/traps.c 65 arch/i386/lib/delay.c 73 arch/i386/mm/fault.c 74 arch/i386/mm/init.c 76 fs/binfmt-elf.c 82 fs/binfmt_java.c 96 fs/exec.c 98 /asm-generic/smplock.h 107 /asm-i386/atomic.h 108 /asm- i386/current.h 109 /asm-i386/dma.h 109 /asm-i386/elf.h 113 /asm-i386/hardirq.h 114 /asm- i386/page.h 114 /asm-i386/pgtable.h 115 /asm-i386/ptrace.h 122 /asm-i386/semaphore.h 123 /asm-i386/shmparam.h 124 /asm-i386/sigcontext.h 125 /asm-i386/siginfo.h 125 /asm-i386/signal.h 127 /asm-i386/smp.h 130 /asm-i386/softirq.h 132 /asm-i386/spinlock.h 133 /asm-i386/system.h 137 /asm-i386/uaccess.h 139 //binfmts.h 146 //capability.h 147 /linux/elf.h 150 /linux/elfcore.h 156 /linux/errupt.h 157 /linux/kernel.h 158 /linux/kernel_stat.h 159 /linux/limits.h 160 /linux/mm.h 160 /linux/module.h 164 /linux/msg.h 168 /linux/personality.h 169 /linux/reboot.h 169 /linux/resource.h 170 /linux/sched.h 171 /linux/sem.h 179 /linux/shm.h 180 /linux/signal.h 181 /linux/slab.h 184 /linux/smp.h 184 /linux/smp_lock.h 185 /linux/swap.h 185 /linux/swapctl.h 187 /linux/sysctl.h 188 /linux/tasks.h 194 /linux/time.h 194 /linux/timer.h 195 /linux/times.h 196 /linux/tqueue.h 196 /linux/wait.h 198 init/.c 198 init/version.c 212 ipc/msg.c 213 ipc/sem.c 218 ipc/shm.c 227 ipc/util.c 236 kernel/capability.c 237 kernel/dma.c 240 kernel/exec_do.c 241 kernel/exit.c 242 kernel/fork.c 248 kernel/info.c 255 kernel/itimer.c 255 kernel/kmod.c 257 kernel/module.c 259 kernel/panic.c 270 kernel/prk.c 271 kernel/sched.c 275 kernel/signal.c 295 kernel/softirq.c 307 kernel/sys.c 307 kernel/sysctl.c 318 kernel/time.c 330 mm/memory.c 335 mm/mlock.c 345 mm/mmap.c 348 mm/mprotect.c 358 mm/mremap.c 361 mm/page_alloc.c 363 mm/page_io.c 368 mm/slab.c 372 mm/swap.c 394 mm/swap_state.c 395 mm/swapfile.c 398 mm/vmalloc.c 406 mm/vmscan.c 409

linux内核IMQ源码实现分析

本文档的Copyleft归wwwlkk所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性,严禁用于任何商业用途。 E-mail: wwwlkk@https://www.sodocs.net/doc/0517577038.html, 来源: https://www.sodocs.net/doc/0517577038.html,/?business&aid=6&un=wwwlkk#7 linux2.6.35内核IMQ源码实现分析 (1)数据包截留并重新注入协议栈技术 (1) (2)及时处理数据包技术 (2) (3)IMQ设备数据包重新注入协议栈流程 (4) (4)IMQ截留数据包流程 (4) (5)IMQ在软中断中及时将数据包重新注入协议栈 (7) (6)结束语 (9) 前言:IMQ用于入口流量整形和全局的流量控制,IMQ的配置是很简单的,但很少人分析过IMQ的内核实现,网络上也没有IMQ的源码分析文档,为了搞清楚IMQ的性能,稳定性,以及借鉴IMQ的技术,本文分析了IMQ的内核实现机制。 首先揭示IMQ的核心技术: 1.如何从协议栈中截留数据包,并能把数据包重新注入协议栈。 2.如何做到及时的将数据包重新注入协议栈。 实际上linux的标准内核已经解决了以上2个技术难点,第1个技术可以在NF_QUEUE机制中看到,第二个技术可以在发包软中断中看到。下面先介绍这2个技术。 (1)数据包截留并重新注入协议栈技术

(2)及时处理数据包技术 QoS有个技术难点:将数据包入队,然后发送队列中合适的数据包,那么如何做到队列中的数

激活状态的队列是否能保证队列中的数据包被及时的发送吗?接下来看一下,激活状态的队列的 证了数据包会被及时的发送。 这是linux内核发送软中断的机制,IMQ就是利用了这个机制,不同点在于:正常的发送队列是将数据包发送给网卡驱动,而IMQ队列是将数据包发送给okfn函数。

读Linux内核源代码

Linux内核分析方法 Linux的最大的好处之一就是它的源码公开。同时,公开的核心源码也吸引着无数的电脑爱好者和程序员;他们把解读和分析Linux的核心源码作为自己的最大兴趣,把修改Linux源码和改造Linux系统作为自己对计算机技术追求的最大目标。 Linux内核源码是很具吸引力的,特别是当你弄懂了一个分析了好久都没搞懂的问题;或者是被你修改过了的内核,顺利通过编译,一切运行正常的时候。那种成就感真是油然而生!而且,对内核的分析,除了出自对技术的狂热追求之外,这种令人生畏的劳动所带来的回报也是非常令人着迷的,这也正是它拥有众多追随者的主要原因: ?首先,你可以从中学到很多的计算机的底层知识,如后面将讲到的系统的引导和硬件提供的中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统保护机制等等,这些都是非都源码不能体会的。 ?同时,你还将从操作系统的整体结构中,体会整体设计在软件设计中的份量和作用,以及一些宏观设计的方法和技巧:Linux的内核为上层应用提供一个与具体硬件不相关的平台; 同时在内核内部,它又把代码分为与体系结构和硬件相关的部分,和可移植的部分;再例如,Linux虽然不是微内核的,但他把大部分的设备驱动处理成相对独立的内核模块,这样减小了内核运行的开销,增强了内核代码的模块独立性。 ?而且你还能从对内核源码的分析中,体会到它在解决某个具体细节问题时,方法的巧妙:如后面将分析到了的Linux通过Botoom_half机制来加快系统对中断的处理。 ?最重要的是:在源码的分析过程中,你将会被一点一点地、潜移默化地专业化。一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。他们总是通过定义大量的宏,来增强代码的清晰度和可读性,而又不增加编译后的代码长度和代码的运行效率; 他们总是在编码的同时,就考虑到了以后的代码维护和升级。甚至,只要分析百分之一的代码后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好者写的。而这一点是任何没有真正分析过标准代码的人都无法体会到的。 然而,由于内核代码的冗长,和内核体系结构的庞杂,所以分析内核也是一个很艰难,很需要毅力的事;在缺乏指导和交流的情况下,尤其如此。只有方法正确,才能事半功倍。正是基于这种考虑,作者希望通过此文能给大家一些借鉴和启迪。 由于本人所进行的分析都是基于2.2.5版本的内核;所以,如果没有特别说明,以下分析都是基于i386单处理器的2.2.5版本的Linux内核。所有源文件均是相对于目录/usr/src/linux的。 方法之一:从何入手 要分析Linux内核源码,首先必须找到各个模块的位置,也即要弄懂源码的文件组织形式。虽然对于有经验的高手而言,这个不是很难;但对于很多初级的Linux爱好者,和那些对源码分析很

Linux内核源代码阅读与工具介绍

Linux的内核源代码可以从很多途径得到。一般来讲,在安装的linux系统下,/usr/src/linux 目录下的东西就是内核源代码。另外还可以从互连网上下载,解压缩后文件一般也都位于linux目录下。内核源代码有很多版本,目前最新的版本是2.2.14。 许多人对于阅读Linux内核有一种恐惧感,其实大可不必。当然,象Linux内核这样大而复杂的系统代码,阅读起来确实有很多困难,但是也不象想象的那么高不可攀。只要有恒心,困难都是可以克服的。任何事情做起来都需要有方法和工具。正确的方法可以指导工作,良好的工具可以事半功倍。对于Linux内核源代码的阅读也同样如此。下面我就把自己阅读内核源代码的一点经验介绍一下,最后介绍Window平台下的一种阅读工具。 对于源代码的阅读,要想比较顺利,事先最好对源代码的知识背景有一定的了解。对于linux内核源代码来讲,基本要求是:⑴操作系统的基本知识;⑵对C语言比较熟悉,最好要有汇编语言的知识和GNU C对标准C的扩展的知识的了解。另外在阅读之前,还应该知道Linux内核源代码的整体分布情况。我们知道现代的操作系统一般由进程管理、内存管理、文件系统、驱动程序、网络等组成。看一下Linux内核源代码就可看出,各个目录大致对应了这些方面。Linux内核源代码的组成如下(假设相对于linux目录): arch这个子目录包含了此核心源代码所支持的硬件体系结构相关的核心代码。如对于X86平台就是i386。 include这个目录包括了核心的大多数include文件。另外对于每种支持的体系结构分别有一个子目录。 init此目录包含核心启动代码。 mm此目录包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于arch/*/mm目录下,如对应于X86的就是arch/i386/mm/fault.c。 drivers系统中所有的设备驱动都位于此目录中。它又进一步划分成几类设备驱动,每一种也有对应的子目录,如声卡的驱动对应于drivers/sound。 ipc此目录包含了核心的进程间通讯代码。 modules此目录包含已建好可动态加载的模块。 fs Linux支持的文件系统代码。不同的文件系统有不同的子目录对应,如ext2文件系统对应的就是ext2子目录。 kernel主要核心代码。同时与处理器结构相关代码都放在arch/*/kernel目录下。 net核心的网络部分代码。里面的每个子目录对应于网络的一个方面。 lib此目录包含了核心的库代码。与处理器结构相关库代码被放在arch/*/lib/目录下。

linux源代码分析实验报告格式

linux源代码分析实验报告格式

Linux的fork、exec、wait代码的分析 指导老师:景建笃 组员:王步月 张少恒 完成日期:2005-12-16

一、 设计目的 1.通过对Linux 的fork 、exec 、wait 代码的分析,了解一个操作系统进程的创建、 执行、等待、退出的过程,锻炼学生分析大型软件代码的能力; 2.通过与同组同学的合作,锻炼学生的合作能力。 二、准备知识 由于我们选的是题目二,所以为了明确分工,我们必须明白进程的定义。经过 查阅资料,我们得知进程必须具备以下四个要素: 1、有一段程序供其执行。这段程序不一定是进程专有,可以与其他进程共用。 2、有起码的“私有财产”,这就是进程专用的系统堆栈空间 3、有“户口”,这就是在内核中有一个task_struct 结构,操作系统称为“进程控制 块”。有了这个结构,进程才能成为内核调度的一个基本单位。同时,这个结构又 是进程的“财产登记卡”,记录着进程所占用的各项资源。 4、有独立的存储空间,意味着拥有专有的用户空间:进一步,还意味着除前述的 系统空间堆栈外,还有其专用的用户空间堆栈。系统为每个进程分配了一个 task_struct 结构,实际分配了两个连续的物理页面(共8192字节),其图如下: Struct task_struct (大约1K) 系统空间堆栈 (大约7KB )两个 连续 的物 理页 面 对这些基本的知识有了初步了解之后,我们按老师的建议,商量分工。如下: 四、 小组成员以及任务分配 1、王步月:分析进程的创建函数fork.c ,其中包含了get_pid 和do_fork get_pid, 写出代码分析结果,并画出流程图来表示相关函数之间的相互调用关系。所占工作 比例35%。 2、张少恒:分析进程的执行函数exec.c,其中包含了do_execve 。写出代码分析结 果,并画出流程图来表示相关函数之间的相互调用关系。所占工作比例35% 。 3、余波:分析进程的退出函数exit.c,其中包含了do_exit 、sys_wait4。写出代码 分析结果,并画出流程图来表示相关函数之间的相互调用关系。所占工作比例30% 。 五、各模块分析: 1、fork.c 一)、概述 进程大多数是由FORK 系统调用创建的.fork 能满足非常高效的生灭机制.除了 0进程等少数一,两个进程外,几乎所有的进程都是被另一个进程执行fork 系统调 用创建的.调用fork 的进程是父进程,由fork 创建的程是子进程.每个进程都有一

Linux源代码分析_存储管理

文章编号:1004-485X (2003)03-0030-04 收稿日期:2003-05-10 作者简介:王艳春,女(1964 ),副教授,主要从事操作系统、中文信息处理等方面的研究工作。 Linux 源代码分析 存储管理 王艳春 陈 毓 葛明霞 (长春理工大学计算机科学技术学院,吉林长春130022) 摘 要:本文剖析了Linux 操作系统的存储管理机制。给出了Linux 存储管理的特点、虚存的实现方法,以及主要数据结构之间的关系。 关键词:Linux 操作系统;存储管理;虚拟存储中图分类号:T P316 81 文献标识码:A Linux 操作系统是一种能运行于多种平台、源代码公开、免费、功能强大、与Unix 兼容的操作系统。自其诞生以来,发展非常迅速,在我国也受到政府、企业、科研单位、大专院校的重视。我们自2000年开始对Linux 源代码(版本号是Linux 2 2 16)进行分析,首先剖析了进程管理和存储管理部分,本文是有关存储管理的一部分。主要介绍了Linux 虚存管理所用到的数据结构及其相互间的关系,据此可以更好地理解其存储管理机制,也可以在此基础上对其进行改进或在此后的研究中提供借鉴作用。作为一种功能强大的操作系统,Linux 实现了以虚拟内存为主的内存管理机制。即能够克服物理内存的局限,使用户进程在透明方式下,拥有比实际物理内存大得多的内存。本文主要阐述了Linux 虚存管理的基本特点和主要实现技术,并分析了Linux 虚存管理的主要数据结构及其相互关系。 1 Lin ux 虚存管理概述 Linux 的内存管理采用虚拟页式管理,使用多级页表,动态地址变换。进程在运行过程中可以动态浮动和扩展,为用户提供了透明的、灵活有效的内存使用方式。 1)32 bit 虚拟地址 在Linux 中,进程的4GB 虚存需通过32 bit 地址进行寻址。Linux 中虚拟地址与线性地址为同一概念,虚拟地址被分成3个子位段,而大小为4k,如图1所示。 2)Linux 的多级页表结构 图1 32位虚拟地址 标准的Linux 的虚存页表为三级页表,依次为页目录(Pag e Directory PGD)、中间页目录(Pag e Middle Directory PMD )、页表(Page Table PT E )。在i386机器上Linux 的页表结构实际为两级,PGD 和PMD 页表是合二为一的。所有有关PMD 的操作关际上是对PGD 的操作。所以源代码中形如*_pgd _*()和*_pmd_*()函数实现的功能也是一样的。 页目录(PGD)是一个大小为4K 的表,每一个进程只有一个页目录,以4字节为一个表项,分成1024个表项(或称入口点),表项的索引即为32位虚拟地址的页目录,该表项的值为所指页表的起始地址。页表(PTE)的每一个入口点的值为此表项所指的一页框(page frame),页表项的索引即为32位虚拟地址中的页号。页框(page reame)并不是物理页,它指的是虚存的一个地址空间。 3) 页表项的格式 图2 Linux 中页目录项和页表项格式 4)动态地址映射 Linux 虚存采用动态地址映射方式,即进程的地址空间和存储空间的对应关系是在程序的执行过 第26卷第3期长春理工大学学报 Vol 26N o 32003年9月 Journal of Changchun University of Science and T echnology Sep.2003

Linux内核源码分析方法

Linux内核源码分析方法 一、内核源码之我见 Linux内核代码的庞大令不少人“望而生畏”,也正因为如此,使得人们对Linux的了解仅处于泛泛的层次。如果想透析Linux,深入操作系统的本质,阅读内核源码是最有效的途径。我们都知道,想成为优秀的程序员,需要大量的实践和代码的编写。编程固然重要,但是往往只编程的人很容易把自己局限在自己的知识领域内。如果要扩展自己知识的广度,我们需要多接触其他人编写的代码,尤其是水平比我们更高的人编写的代码。通过这种途径,我们可以跳出自己知识圈的束缚,进入他人的知识圈,了解更多甚至我们一般短期内无法了解到的信息。Linux内核由无数开源社区的“大神们”精心维护,这些人都可以称得上一顶一的代码高手。透过阅读Linux 内核代码的方式,我们学习到的不光是内核相关的知识,在我看来更具价值的是学习和体会它们的编程技巧以及对计算机的理解。 我也是通过一个项目接触了Linux内核源码的分析,从源码的分析工作中,我受益颇多。除了获取相关的内核知识外,也改变了我对内核代码的过往认知: 1.内核源码的分析并非“高不可攀”。内核源码分析的难度不在于源码本身,而在于如何使用更合适的分析代码的方式和手段。内核的庞大致使我们不能按照分析一般的demo程序那样从主函数开始按部就班的分析,我们需要一种从中间介入的手段对内核源码“各个击破”。这种“按需索取”的方式使得我们可以把握源码的主线,而非过度纠结于具体的细节。 2.内核的设计是优美的。内核的地位的特殊性决定着内核的执行效率必须足够高才可以响应目前计算机应用的实时性要求,为此Linux内核使用C语言和汇编的混合编程。但是我们都 知道软件执行效率和软件的可维护性很多情况下是背道而驰的。如何在保证内核高效的前提下提高内核的可维护性,这需要依赖于内核中那些“优美”的设计。 3.神奇的编程技巧。在一般的应用软件设计领域,编码的地位可能不被过度的重视,因为开发者更注重软件的良好设计,而编码仅仅是实现手段问题——就像拿斧子劈柴一样,不用太多的思考。但是这在内核中并不成立,好的编码设计带来的不光是可维护性的提高,甚至是代码性能的提升。 每个人对内核的了理解都会有所不同,随着我们对内核理解的不断加深,对其设计和实现的思想会有更多的思考和体会。因此本文更期望于引导更多徘徊在Linux内核大门之外的人进入Linux的世界,去亲自体会内核的神奇与伟大。而我也并非内核源码方面的专家,这么做也只是希望分享我自己的分析源码的经验和心得,为那些需要的人提供参考和帮助,说的“冠冕堂皇”一点,也算是为计算机这个行业,尤其是在操作系统内核方面贡献自己的一份绵薄之力。闲话少叙(已经罗嗦了很多了,囧~),下面我就来分享一下自己的Linix内核源码分析方法。 二、内核源码难不难? 从本质上讲,分析Linux内核代码和看别人的代码没有什么两样,因为摆在你面前的一般都不是你自己写出来的代码。我们先举一个简单的例子,一个陌生人随便给你一个程序,并要你看完源码后讲解一下程序的功能的设计,我想很多自我感觉编程能力还可以的人肯定觉得这没什么,只要我耐心的把他的代码从头到尾看完,肯定能找到答案,并且事实确实是如此。那么现在换一个假设,如果这个人是Linus,给你的就是Linux内核的一个模块的代码,你还会觉得依然那么 轻松吗?不少人可能会有所犹豫。同样是陌生人(Linus要是认识你的话当然不算,呵呵~)给 你的代码,为什么给我们的感觉大相径庭呢?我觉得有以下原因:

Linux内核源代码解读

Linux内核源代码解读!! 悬赏分:5 - 提问时间2007-1-24 16:28 问题为何被关闭 赵炯书中,Bootsect代码中有 mov ax , #BOOTSEG 等 我曾自学过80x86汇编,没有见过#的用法,在这为什么要用#? 另外, JMPI 的用法是什么?与JMP的区别是什么? 提问者: Linux探索者 - 一级 答复共 1 条 检举 系统初始化程序 boot.s 的分析 [转] 系统初始化程序 boot.s 的分析: 阚志刚,2000/03/20下午,在前人的基础之上进行整理完善 ******************************************************************************** ************** boot.s is loaded at 0x7c00 by the bios-startup routines, and moves itself out of the way to address 0x90000, and jumps there. 当PC 机启动时,Intel系列的CPU首先进入的是实模式,并开始执行位于地址0xFFF0处的代码,也就是ROM-BIOS起始位置的代码。BIOS先进行一系列的系统自检,然后初始化位于地址0的中断向量表。最后BIOS将启动盘的第一个扇区装入0x7C00(31K;0111,1100,0000,0000),并开始执行此处的代码。这就是对内核初始化过程的一个最简单的描述。 最初,Linux核心的最开始部分是用8086汇编语言编写的。当开始运行时,核心将自己装入到绝对地址0x90000(576K; 1001,0000,0000,0000,0000),再将其后的2k字节装入到地址0x90200(576.5k;1001,0000,0010,0000,0000)处,最后将核心的其余部分装入到0x10000(64k; 1,0000,0000,0000,0000). It then loads the system at 0x10000, using BIOS interrupts. Thereafter it disables all interrupts, moves the system down to 0x0000, changes to protected mode, and calls the start of system. System then must RE-initialize the protected mode in it's own tables, and enable interrupts as needed. 然后,关掉所有中断,把系统下移到0x0000(0k;0000,0000,0000,0000,0000)处,改变到保护模式,然后开始系统的运行.系统必须重新在保护模式下初始化自己的系统表格,并且打开所需的中断. NOTE 1! currently system is at most 8*65536(8*64k=512k; 1000,0000,0000,0000,0000) bytes long. This should be no problem, even in the future. I want to keep it simple. This 512 kB kernel size should be enough - in fact more would mean we'd have to move not just these start-up routines, but also do something about the cache-memory

Linux KVM虚拟化源代码分析文档

KVM虚拟机源代码分析 1,KVM结构及工作原理 1.1K VM结构 KVM基本结构有两部分组成。一个是KVM Driver ,已经成为Linux 内核的一个模块。负责虚拟机的创建,虚拟内存的分配,虚拟CPU寄存器的读写以及虚拟CPU的运行等。另外一个是稍微修改过的Qemu,用于模拟PC硬件的用户空间组件,提供I/O设备模型以及访问外设的途径。 图1 KVM基本结构 KVM基本结构如图1所示。其中KVM加入到标准的Linux内核中,被组织成Linux中标准的字符设备(/dev/kvm)。Qemu通KVM提供的LibKvm应用程序接口,通过ioctl系统调用创建和运行虚拟机。KVM Driver使得整个Linux成为一个虚拟机监控器。并且在原有的Linux两种执行模式(内核模式和用户模式)的基础上,新增加了客户模式,客户模式拥有自己的内核模式和用户模式。在虚拟机运行下,三种模式的分工如下: 客户模式:执行非I/O的客户代码。虚拟机运行在客户模式下。 内核模式:实现到客户模式的切换。处理因为I/O或者其它指令引起的从客户模式的退出。KVM Driver工作在这种模式下。 用户模式:代表客户执行I/O指令Qemu运行在这种模式下。

在KVM模型中,每一个Guest OS 都作为一个标准的Linux进程,可以使用Linux的进程管理指令管理。 在图1中./dev/kvm在内核中创建的标准字符设备,通过ioctl系统调用来访问内核虚拟机,进行虚拟机的创建和初始化;kvm_vm fd是创建的指向特定虚拟机实例的文件描述符,通过这个文件描述符对特定虚拟机进行访问控制;kvm_vcpu fd指向为虚拟机创建的虚拟处理器的文件描述符,通过该描述符使用ioctl系统调用设置和调度虚拟处理器的运行。 1.2K VM工作原理 KVM的基本工作原理:用户模式的Qemu利用接口libkvm通过ioctl系统调用进入内核模式。KVM Driver为虚拟机创建虚拟内存和虚拟CPU后执行VMLAUCH指令进入客户模式。装载Guest OS执行。如果Guest OS发生外部中断或者影子页表缺页之类的事件,暂停Guest OS的执行,退出客户模式进行一些必要的处理。然后重新进入客户模式,执行客户代码。如果发生I/O事件或者信号队列中有信号到达,就会进入用户模式处理。KVM采用全虚拟化技术。客户机不用修改就可以运行。 图2 KVM 工作基本原理

怎样读Linux内核源代码

Linux内核分析方法 2010-9-12 Linux的最大的好处之一就是它的源码公开。同时,公开的核心源码也吸引着无数的电脑爱好者和程序员;他们把解读和分析Linux的核心源码作为自己的最大兴趣,把修改Linux 源码和改造Linux系统作为自己对计算机技术追求的最大目标。 Linux内核源码是很具吸引力的,特别是当你弄懂了一个分析了好久都没搞懂的问题;或者是被你修改过了的内核,顺利通过编译,一切运行正常的时候。那种成就感真是油然而生!而且,对内核的分析,除了出自对技术的狂热追求之外,这种令人生畏的劳动所带来的回报也是非常令人着迷的,这也正是它拥有众多追随者的主要原因: ?首先,你可以从中学到很多的计算机的底层知识,如后面将讲到的系统的引导和硬件提供的中断机制等;其它,象虚拟存储的实现机制,多任务机制,系统保护机制 等等,这些都是非都源码不能体会的。 等等,这些都是非读源码不能体会的。 ?同时,你还将从操作系统的整体结构中,体会整体设计在软件设计中的份量和作用,以及一些宏观设计的方法和技巧:Linux的内核为上层应用提供一个与具体硬件不 相关的平台;同时在内核内部,它又把代码分为与体系结构和硬件相关的部分,和 可移植的部分;再例如,Linux虽然不是微内核的,但他把大部分的设备驱动处理 成相对独立的内核模块,这样减小了内核运行的开销,增强了内核代码的模块独立 性。 ?而且你还能从对内核源码的分析中,体会到它在解决某个具体细节问题时,方法的巧妙:如后面将分析到了的Linux通过Botoom_half机制来加快系统对中断的处理。 ?最重要的是:在源码的分析过程中,你将会被一点一点地、潜移默化地专业化。一个专业的程序员,总是把代码的清晰性,兼容性,可移植性放在很重要的位置。他 们总是通过定义大量的宏,来增强代码的清晰度和可读性,而又不增加编译后的代 码长度和代码的运行效率;他们总是在编码的同时,就考虑到了以后的代码维护和 升级。甚至,只要分析百分之一的代码后,你就会深刻地体会到,什么样的代码才是一个专业的程序员写的,什么样的代码是一个业余爱好者写的。而这一点是任何 没有真正分析过标准代码的人都无法体会到的。 然而,由于内核代码的冗长,和内核体系结构的庞杂,所以分析内核也是一个很艰难,很需要毅力的事;在缺乏指导和交流的情况下,尤其如此。只有方法正确,才能事半功倍。正是基于这种考虑,作者希望通过此文能给大家一些借鉴和启迪。 由于本人所进行的分析都是基于2.2.5版本的内核;所以,如果没有特别说明,以下分析都是基于i386单处理器的 2.2.5版本的Linux内核。所有源文件均是相对于目录/usr/src/linux的。 方法之一:从何入手

Linux内核源代码漫游

Li nu x内核源代码漫游创建时间:2001-10-1121时13分 Linux内核源代码漫游 Alessandro Rubini 著, rubini@pop.systemy.it 赵 炯译,gohigh@https://www.sodocs.net/doc/0517577038.html, (https://www.sodocs.net/doc/0517577038.html,) 本章试图以顺序的方式来解释Li nu x源代码,以帮助读者对源代码的体系结构以及很多相关的unix特性的实现有一个很好的理解。目标是帮助对Lin ux不甚了解的有经验的C 程序员对整个L i nu x的设计有所了解。这也就是为什么内核漫游的入点选择为内核本身的启始点:系统引导(启动)。 这份材料需要对C语言以及对Un i x的概念和P C机的结构有很好的了解,然而本章中并没有出现任何的C代码,而是直接参考(指向)实际的代码的。有关内核设计的最佳篇幅是在本手册的其它章节中,而本章仍趋向于是一个非正式的概述。 本章中所参阅的任何文件的路径名都是指主源代码目录树,通常是/u s r/s r c/li nu x。 这里所给出的大多数信息都是取之于Lin u x发行版 1.0的源代码。虽然如此, 有时也会提供对后期版本的参考。这篇漫游中开头有图标的任何小节都是强调 1.0版本后对内核的新的改动。如果没有这样的小节存在,则表示直到版本 1.0.9-1.1.76,没有作过改动。 有时候本章中会有象这样的小节,这是指向正确的代码以对刚讨论过的主题取得 更多信息的指示符。当然,这里是指源代码。 引导(启动)系统 当P C的电源打开后,80x86结构的CP U将自动进入实模式,并从地址0xF FFF0开始自动执行程序代码,这个地址通常是ROM-B IOS中的地址。PC机的BIOS将执行某些系统的检测,在物理地址0处开始初始化中断向量。此后,它将可启动设备的第一个扇区读入内存地址0x7C00处,并跳转到这个地方。启动设备通常是软驱或是硬盘。这里的叙述是非常简单的,但这已经足够理解内核初始化的工作过程了。 Li nux的最最前面部分是用8086汇编语言编写的(bo ot/bo ot s e c t.S),它将由BIOS 读入到内存0x7C00处,当它被执行时就会把自己移到绝对地址0x90000处,并将启动设备 (bo ot/s et u p.S)的下2k B字节的代码读入内存0x90200处,而内核的其它部分则被读入到地址0x10000处。在系统加载期间将显示信息"L oa d in g..."。然后控制权将传递给bo ot/S et u p.S中的代码,这是另一个实模式汇编语言程序。 启动部分识别主机的某些特性以及vg a卡的类型。如果需要,它会要求用户为控制台选择显示模式。然后将整个系统从地址0x10000移至0x1000处,进入保护模式并跳转至系统的余下部分(在0x1000处)。 下一步是内核的解压缩。0x1000处的代码来自于z B oo t/head.S,它初始化寄存器并调用d e c om p r e ss_k e rn e l(),它们依次是由z B oo t/i n f l at e.c、z B oot/u n z i p.c和z B oo t/m isc.c组成。被解压的数据存放到了地址0x10000处(1兆),这也是为什么Li nu x不能运行于少于2兆内存的主要原因。[在1兆内存中解压内核的工作已经完成,见M em o r y S av e rs--ED]将内核封装在一个gz i p文件中的工作是由z B oo t目录中的M ak ef il e以及工具 完成的。它们是值得一看的有趣的文件。 内核发行版1.1.75将b oot和z B oo t目录下移到了a rc h/i386/boo t中了,这个改动意味着对不同的体系结构允许真正的内核建造,不过我将仍然只讲解有关i386的信息。解压过的代码是从地址0x10100处开始执行的[这里我可能忘记了具体的物理地址 了, 第 1 页共 9 页

Linux 源代码分析

Linux内核(2.6.13.2)源代码分析 苗彦超 摘要: 1系统启动 1.1汇编代码head.S及以前 设置CPU状态初值,创建进程0,建立进程堆栈: movq init_rsp(%rip), %rsp,init_rsp定义 .globl init_rsp init_rsp: .quad init_thread_union+THREAD_SIZE-8 即将虚地址init_thread_union+THREAD_SIZE-8作为当前进程(进程0)核心空间堆栈栈底,init_thread_union定义于文件arch/x86_64/kernel/init_task.c中: union thread_union init_thread_union __attribute__((__section__(".data.init_task"))) = {INIT_THREAD_INFO(init_task)}; INIT_THREAD_INFO定义于文件include/asm-x86_64/thread_info.h中,初始化init_thread_union.task = &init_task,init_task同样定义于文件init_task.c中,初始化为: struct task_struct init_task = INIT_TASK(init_task); INIT_TASK宏在include/linux/init_task.h中定义。 全部利用编译时静态设置的初值,将进程0的控制结构设置完成,使进程0可以按普通核心进程访问。 init_task.mm = NULL; init_task.active_mm = INIT_MM(init_mm), init_https://www.sodocs.net/doc/0517577038.html,m = “swapper” INIT_MM将init_mm.pgd初始化为swapper_pg_dir,即init_level4_pgt,定义与head.S中。进程0的名称为swapper。 利用下述汇编代码跳转到C函数执行: movl %esi, %edi// 传递函数参数 movq initial_code(%rip),%rax jmp *%rax initial_code: .quad x86_64_start_kernel 开始执行文件arch/x86_64/kernel/head64.c中的C函数x86_64_start_kernel(char * real_mode_data),1.2函数x86_64_start_kernel(char * real_mode_data) 1设置全部中断向量初始入口为early_idt_handler,加载中断描述符idt_descr 2clear_bss():BSS段清0 3pda_init(0):设置处理器0相关信息(processor datastructure area ?),重置CR3为init_level4_pgt 4copy_bootdata:复制BIOS启动参数到操作系统变量x86_boot_params中,再复制启动命令行参数由x86_boot_params到saved_command_line中,用printk显示saved_command_line,从此不再 与实模式数据打交道 5cpu_set:设置CPU 0 开始工作标志 6处理“earlyprintk=”、“numa”、“disableapic”等命令行参数 7setup_boot_cpu_data():设置CPU信息结构boot_cpu_data,使用cpuid指令

LINUX2.6内核代码分析――进程管理

摘要:随着计算机开发以及教学工作的深入,大家也不可避免的要接触到基于linux内核的各种操作系统。如何迈入linux的大门,并充分利用linux开源、灵活等特性呢?解读内核源码无疑是理解并掌握linux的关键。本篇文章,主要是对linux内核进程管理部分进行笼统的解读,帮助读者快速掌握linux进程管理的主线,对读者的理解起到抛砖引玉的作用。 关键词:linux2.6;内核代码;进程管理 一 linux是最受欢迎的自由电脑操作系统内核。它是一个用c语言写成,符合posix标准的类unix操作系统。linux最早是由芬兰黑客 linus torvalds为尝试在英特尔x86架构上提供自由免费的类unix操作系统而开发的。技术上说linux是一个内核。“内核”指的是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。一个内核不是一套完整的操作系统。一套基于linux内核的完整操作系统叫作linux操作系统,或是gnu/linux。 linux内核的主要模块(或组件)分以下几个部分:存储管理、cpu和进程管理、文件系统、设备管理和驱动、网络通信,以及系统的初始化(引导)、系统调用等。一般地,可以从linux 内核版本号来区分系统是否是linux稳定版还是测试版。以版本2.4.0为例,2代表主版本号,4代表次版本号,0代表改动较小的末版本号。在版本号中,序号的第二位为偶数的版本表明这是一个可以使用的稳定版本,如2.2.5,而序号的第二位为奇数的版本一般有一些新的东西加入,是个不一定很稳定的测试版本,如2.3.1。这样稳定版本来源于上一个测试版升级版本号,而一个稳定版本发展到完全成熟后就不再发展。本文是针对2.4.0版本内核进行分析。有于篇幅有限阅读前需要读者自行下载相应内核源码。 二 schedule()函数首先对所有进程进行检测,唤醒任何一个已经得到信号的任务。主要是任务数组中的每个进程,检测其报警定时值alarm。若alarmnr_active + expired->nr_active ii.prio_array_t *active, *expired, arrays[2];// 两个子队列 就绪队列根据时间片是否被用完分为了active队列和expired队列。queue是指定优先级进程list的指针,如queue[i]就是priority为 i 的进程的指针。bitmap是一张优先级的位图,或者可以说的位数组,每一位代表了一个优先级(类似uc/os-ii)。 max_prio指的是优先级的数量. 以上是对linux 2.4.0版本内核源码进程管理部分的概括分析,主要用来为linux源码解读做一个引导,起到抛砖引玉的作用。但是由于时间,篇幅等种种原因,无法将全部函数调用以及相关代码一一呈现在读者面前,还望见谅。

linux内核编译详细教程

详细教程:编译Linux最新内核 一、实验目的 学习重新编译Linux内核,理解、掌握Linux内核和发行版本的区别。 二、实验内容 在Linux操作系统环境下重新编译内核。实验主要内容: A. 查找并且下载一份内核源代码,本实验使用最新的Linux内核2.6.36。 B. 配置内核。 C. 编译内核和模块。 D. 配置启动文件。 本次实验环境是Linux2.6.35内核的环境下,下载并重新编译内核源代码(2.6.36);然后,配置GNU的启动引导工具grub,成功运行编译成功的内核。 三、主要仪器设备(必填) Linux环境:utuntu10.10,linux内核2.6.35 待编译内核:linux2.6.36 四、操作方法和实验步骤 【1】下载内核源代码 从https://www.sodocs.net/doc/0517577038.html,/newlinux/files/jijiangmin网站上下载最新的Linux内核2.6.36。【2】部署内核源代码

打开终端,更改用户权限为root。具体做法是在终端输入sudo su,然后按提示输入密码。判断是否是root用户是使用whoami命令,若输出为root则已经切换到root账户。 输入mv linux-2.6.36.tar.gz /usr/src,目的是把下载的内核源代码文件移到/usr/src目录。 输入cd /usr/src切换到该目录下。 输入tar zxvf linux-2.6.36.tar.gz,目的是解压内核包,生成的源代码放在linux-2.6.36目录下。 输入cd linux-2.6.36,切换到该目录下。 输入cp /boot/config-,然后按下Tab键,系统会自动填上该目录下符合条件的文件名,然后继续输入.config,目的是使用在boot目录下的原配置文件。 【3】配置内核 配置内核的方法很多,主要有如下几种: #make menuconfig //基于ncurse库编制的图形工具界面 #make config //基于文本命令行工具,不推荐使用 #make xconfig //基于X11图形工具界面 #make gconfig //基于gtk+的图形工具界面 由于对Linux还处在初学阶段,所以选择了简单的配置内核方法,即make menuconfig。在终端输入make menuconfig,等待几秒后,终端变成图形化的内核配置界面。进行配置时,大部分选项使用其缺省值,只有一小部分需要根据不同的需要选择。 对每一个配置选项,用户有三种选择,它们分别代表的含义如下: <*>或[*]——将该功能编译进内核 []——不将该功能编译进内核

相关主题