《课程设计论文Linux内核初起代码分析.doc》由会员分享,可在线阅读,更多相关《课程设计论文Linux内核初起代码分析.doc(20页珍藏版)》请在三一办公上搜索。
1、Linux内核初起代码分析 计算机科学与工程学院课程设计报告题目全称:Linux内核初起代码分析 学生学号: 姓名: 指导老师: 职称: 指导老师评语: 签字: 课程设计成绩:设计过程表现设计报告质量总分目录摘 要1第一章 引 言11.1 问题的提出11.2任务与分析1第二章 代码分析22.1系统初始化过程流程22.2 数据结构22.3常量和出错信息的意义42.4调用关系图42.5各模块/函数的功能及详细框图52.5.1 static void time_init(void)分析62.5.2 void main(void)分析62.5.3 pause()分析82.5.4 static int
2、printf(const char *fmt, .)分析82.5.5 void init(void)分析9第三章 内核调试123.1 运行环境123.2 编译内核过程12第四章 总结与体会15致 谢16参考文献17摘 要随着计算机的普及,计算机发挥着越来越重要的作用,计算机的使用也越来越普遍,所以让更多的人能够更好的使用和掌握一些计算机方法显得十分重要。充分发挥计算机的作用也显得十分重要。操作系统应运而生。操作系统是一种软件,用来帮助其他的程序控制计算机并和用户进行交互。因而,对操作系统的研究是很有必要的。操作系统包含了多个部分或者组件,最核心的部分是内核。其他的部分用来帮助内核完成计算机资源
3、的管理和应用程序的控制。Linux操作系统是使用很广泛的,高质量的一个操作系统。此次起始代码分析,我分析了init/main.c文件中的main()、init()以及编译内核代码。main()中主要是关于起始的调用和设备和系统信息初始化,以及创建进程。此时中断仍被禁止着,做完必要的设置后就将其开启init()是创建进程,并检测是否出错,出错则再次创建执行并打印出出错信息。init()函数运行在任务0 第1 次创建的子进程(任务1)中。它首先对第一个将要执行的程序(shell)的环境进行初始化,然后加载该程序并执行之。对Linux 初起代码的分析有助于了解操作系统的启动,可以更好地理解和认识操作
4、系统是如何管理计算机资源的。关键词:操作系统;Linux;初起代码Linux内核初起代码分析第一章 引 言 1.1 问题的提出 操作系统是一种软件,用来帮助其他的程序控制计算机并和用户进行交互。操作系统包含了众多程序用来控制计算机的核心功能,并且操作系统是链接用户和计算机硬件的桥梁,便于人们有效管理。尽管在过去操作系统取得了长足的进步,但是基本的目标并未改变:通过使用操作系统来处理公共任务,程序员便可以更容易地编写应用程序。应用程序是一种软件,用来向计算机的用户提供某种服务,而不仅仅是控制计算机硬件。尽管在外观上和功能上有所不同,但是所有的操作系统都具有一些相同之处:初始化计算机硬件,以便操作
5、系统和其他持续可以正常工作;为使用操作系统的程序分配系统资源,如内存和处理时间;跟踪调试运行的多个程序;为所有使用系统设备的程序提供规范的访问接口。操作系统包含了多个部分或者组件,最核心的部分是内核。其他的部分用来帮助内核完成计算机资源的管理和应用程序的控制。操作系统控制了计算机上运行的各种应用程序。没有操作系统各类函数的调用,应用程序就无法执行。因而,对操作系统的研究是很有必要的。 Linux操作系统是使用很广泛的,高质量的一个操作系统,而且作为一个开源的系统,可以很方便的查看起代码并进行分析,有利于更好的认识和了解操作系统。此次对Linux 初起代码的分析有助于了解操作系统的启动,可以更好
6、地理解和认识操作系统是如何管理计算机资源的。1.2任务与分析 本课题主要的目的是了解一个操作系统的初起过程。根据操作系统的基础知识,分析init/main.c中关于系统初起的相关代码,了解一个操作系统的初起过程,得到相关的框图,写出设计说明书。1) 代码分析结果, 包括但不限于:2) 数据结构3) 常量和出错信息的意义4) 调用关系图5) 各模块/函数详细框图分析思路:1) 了解基础知识,找到相关的源码;2) 对代码充分阅读,先得到单个函数的数据结构和框图;3) 将多个函数的框图汇总,绘出整体的框图;使用的源代码是Linux/init/main.c (C) 1991 Linus Torvald
7、s第二章 代码分析 2.1系统初始化过程流程系统整个初始化过程见图2.1所示:进程n进程1开始系统初始化对物理内存各部分进行功能划分和分配系统各个部分初始化,包括对任务0初始化移到任务0中执行创建进程1(init)空闲时执行pause()加载根文件系统设置终端标准IO创建进程2循环等待进程2退出创建子进程循环等待进程结束任务进程0终端输入定向到rc执行shell退 出设置终端标准IO执行shell退 出进程2图2.1 内核初始化程序流程示意图2.2 数据结构1) 时间结构:#define CLOCKS_PER_SEC 100/* 系统时钟滴答频率,100HZ */typedef long cl
8、ock_t;/* 从进程开始系统经过的时钟滴答数 */struct tm int tm_sec;/* 秒数 0,59 */ int tm_min;/* 分钟数 0,59 */ int tm_hour;/* 小时数 0,59 */ int tm_mday;/* 1 个月的天数 0,31 */ int tm_mon;/* 1 年中月份 0,11 */ int tm_year;/* 从1900 年开始的年数 */ int tm_wday;/* 1 星期中的某天 0,6(星期天=0) */ int tm_yday;/* 1 年中的某天 0,365 */ int tm_isdst;/* 夏令时标志 */
9、;2) 存放硬盘参数表信息:struct drive_info char dummy32;drive_info;/* 用于存放硬盘参数表信息 */3) tty 等待队列数据结构和tty 数据结构:struct tty_queue unsigned long data;/* 等待队列缓冲区中当前数据指针字符数 */ unsigned long head;/* 缓冲区中数据头指针 */ unsigned long tail;/* 缓冲区中数据尾指针 */ struct task_struct *proc_list; /* 等待进程列表 */ char bufTTY_BUF_SIZE;/* 队列的缓
10、冲区 */;struct tty_struct /* tty 数据结构 */ struct termios termios;/* 终端io 属性和控制字符数据结构 */ int pgrp;/* 所属进程组 */ int stopped;/* 停止标志 */ void (*write) (struct tty_struct * tty); /* tty 写函数指针 */ struct tty_queue read_q;/* tty 读队列 */ struct tty_queue write_q; /* tty 写队列 */ struct tty_queue secondary; /* tty 辅
11、助队列(存放规范模式字符序列) */;/* 可称为规范(熟)模式队列 */1)2)3)4) 请求队列中项的结构和块设备结构:struct request/* 请求队列中项的结构。其中如果dev=-1,则表示该项没有被使用 */ int dev;/* 使用的设备号 */ int cmd;/* 命令(READ或 WRITE) */ int errors;/* 操作时产生的错误次数 */ unsigned long sector;/* 起始扇区(1块=2扇区) */ unsigned long nr_sectors; /* 读/写扇区数 */ char *buffer;/* 数据缓冲区 */ str
12、uct task_struct *waiting; /* 任务等待操作执行完成的地方 */ struct buffer_head *bh;/* 缓冲区头指针(include/Linux/fs.h,68) */ struct request *next;/* 指向下一请求项 */;struct blk_dev_struct/* 块设备结构 */ void (*request_fn) (void); /* 请求操作的函数指针 */ struct request *current_request; /* 请求信息结构 */;2.3常量和出错信息的意义定义系统调用嵌入式汇编宏函数。不带参数的系统调用宏
13、函数。type name(void)。%0 - eax(_res),%1 - eax(_NR_#name)。其中name 是系统调用的名称,与 _NR_ 组合形成上面的系统调用符号常数,从而用来对系统调用表中函数指针寻址。返回:如果返回值大于等于0,则返回该值,否则置出错号errno,并返回-1。#define _syscall0(type,name) type name(void) long _res; _asm_ volatile ( int $0x80 /* 调用系统中断0x80 */:=a (_res) /* 返回值eax(_res) */: (_NR_#name); /* 输入为系统
14、中断调用号_NR_name */ if (_res = 0) /* 如果返回值=0,则直接返回该值 */ return (type) _res; errno = -_res; /* 否则置出错号,并返回-1 */ return -1;/* 有1 个参数的系统调用宏函数。type name(atype a) */* %0 - eax(_res),%1 - eax(_NR_name),%2 - ebx(a) */#define _syscall1(type,name,atype,a) type name(atype a) long _res; _asm_ volatile ( int $0x80
15、: =a (_res) : (_NR_#name), b (long)(a); if (_res = 0) return (type) _res; errno = -_res; return -1; extern int errno;/* 出错号,全局变量 */static inline _syscall0(int,fork)/*这是unistd.h 中的内嵌宏代码。以嵌入汇编的形式调用Linux 的系统调用中断0x80。该中断是所有系统调用的入口。该条语句实际上是int fork()创建进程系统调用。syscall0 名称中最后的0 表示无参数,1 表示1 个参数 */static inli
16、ne _syscall0(int,pause) /* int pause()系统调用:暂停进程的执行,直到收到一个信号 */static inline _syscall1(int,setup,void *,BIOS)/* int setup(void * BIOS)系统调用,仅用于Linux 初始化(仅在这个程序中被调用)*/static inline _syscall0(int,sync) /* int sync()系统调用更新文件系统 */2.4调用关系图在内核源代码的init/目录中只有一个main.c 文件。系统在执行完boot/目录中的head.s 程序后就会将执行权交给main.c
17、。该程序虽然不长,但却包括了内核初始化的所有工作。main.c 程序首先利用前面setup.s 程序取得的系统参数设置系统的根文件设备号以及一些内存全局变量。这些内存变量指明了主内存的开始地址、系统所拥有的内存容量和作为高速缓冲区内存的末端地址。如果还定义了虚拟盘(RAMDISK),则主内存将适当减少。整个内存的映像示意图见图3.1 所示。内核程序高速缓存主内存区虚拟盘图2.1 系统中内存功能划分示意图图中,高速缓冲部分还要扣除被显存和ROM BIOS 占用的部分。高速缓冲区是用于磁盘等块设备临时存放数据的地方,以1K(1024)字节为一个数据块单位。主内存区域的内存是由内存管理模块mm通过分
18、页机制进行管理分配,以4K 字节为一个内存页单位。内核程序可以自由访问高速缓冲中的数据,但需要通过mm 才能使用分配到的内存页面。然后,内核进行所有方面的硬件初始化工作。包括陷阱门、块设备、字符设备和tty,包括人工设置第一个任务(task 0)。待所有初始化工作完成后就设置中断允许标志以开启中断,main()也切换到了任务0 中运行。在整个内核完成初始化后,内核将执行权切换到了用户模式(任务0),也即CPU 从0 特权级切换到了第3 特权级。此时main.c 的主程序就工作在任务0 中。然后系统第一次调用进程创建函数fork(),创建出一个用于运行init()的子进程。2.5各模块/函数的功
19、能及详细框图该程序首先确定如何分配使用系统物理内存,然后调用内核各部分的初始化函数分别对内存管理、中断处理、块设备和字符设备、进程管理以及硬盘和软盘硬件进行初始化处理。在完成了这些操作之后,系统各部分已处于可运行状态。此后程序把自己“手工”移动到任务0(进程0)中运行,并使用fork()调用首次创建出进程1(init 进程)。在init进程中程序将继续进行应用环境的初始化并执行shell 登录程序。而原进程0则会在系统空闲时被调度执行,此时任务0仅执行pause()系统调用,并又会调用调度函数。在init 进程中,如果终端环境建立成功,则会再生成一个子进程(进程2),用于运行shell 程序/
20、bin/sh。若该子进程退出,则父进程进入一个死循环内,继续生成子进程,并在此子进程中再次执行shell 程序/bin/sh,而父进程则继续等待。由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现的,因此在首次使用fork()创建新进程init 时,为了确保新进程用户态堆栈没有进程0 的多余信息,要求进程0 在创建首个新进程之前不要使用用户态堆栈,也即要求任务0 不要调用函数。因此在main.c 主程序移动到任务0 执行后,任务0 中的代码fork()不能以函数形式进行调用。程序中实现的方法是采用gcc 函数内嵌的形式来执行这个系统调用。通过申明一个内嵌(inline)函数,可以
21、让gcc 把函数的代码集成到调用它的代码中。这会提高代码执行的速度,因为省去了函数调用的开销。另外,如果任何一个实际参数是一个常量,那么在编译时这些已知值就可能使得无需把内嵌函数的所有代码都包括进来而让代码也得到简化。另外,任务0 中的pause()也需要使用函数内嵌形式来定义。如果调度程序首先执行新创建的子进程init,那么pause()采用函数调用形式不会有什么问题。但是内核调度程序执行父进程(进程0)和子进程init 的次序是随机的,在创建了init 后有可能首先会调度进程0 执行。因此pause()也必须采用宏定义来实现。对于Linux 来说,所有任务都是在用户模式运行的,包括很多系统
22、应用程序,如shell 程序、网络子系统程序等。内核源代码lib/目录下的库文件就是专门为这里新创建的进程提供支持函数的。2.5.1 static void time_init(void)分析该子程用于读取取CMOS 时钟,并设置开机时间startup_time(秒)。struct tm time; /* 时间结构tm 定义在include/time.h 中 */ do time.tm_sec = CMOS_READ(0); /* 当前时间秒值(均是BCD 码值)*/ time.tm_min = CMOS_READ(2); /* 当前分钟值 */ time.tm_hour = CMOS_REA
23、D(4); /* 当前小时值 */ time.tm_mday = CMOS_READ(7); /* 一月中的当天日期 */ time.tm_mon = CMOS_READ(8); /* 当前月份(112)*/ time.tm_year = CMOS_READ(9); /* 当前年份 */ while (time.tm_sec != CMOS_READ(0);CMOS 的访问速度很慢。为了减小时间误差,在读取了下面循环中所有数值后,若此时CMOS 中秒值发生了变化,那么就重新读取所有值。 BCD_TO_BIN(time.tm_sec); /* 转换成二进制数值 */ BCD_TO_BIN(tim
24、e.tm_min); BCD_TO_BIN(time.tm_hour); BCD_TO_BIN(time.tm_mday); BCD_TO_BIN(time.tm_mon); BCD_TO_BIN(time.tm_year); time.tm_mon-; /* tm_mon 中月份范围是011 */startup_time = kernel_mktime(&time); /* 调用kernel/mktime.c 中函数,计算从1970年1月1日0时起到开机当日经过的秒数,作为开机时间 */2.5.2 void main(void)分析main()函数中完成启动时对设备内核初始化,以及创建进程。
25、此时中断仍被禁止着,做完必要的设置后就将其开启。下面这段代码用于保存:根设备号:ROOT_DEV; 高速缓存末端地址:buffer_memory_end;机器内存:memory_end;主内存开始地址 :main_memory_start; ROOT_DEV = ORIG_ROOT_DEV; /* ROOT_DEV 定义在fs/super.c */ drive_info = DRIVE_INFO; /* 复制0x90080 处的硬盘参数表 */ memory_end = (120) + (EXT_MEM_K 16*1024*1024) /* 如果内存超过16Mb,则按16Mb 计 */ mem
26、ory_end = 16*1024*1024; if (memory_end 12*1024*1024) /* 如果内存12Mb,则设置缓冲区末端=4Mb */ buffer_memory_end = 4*1024*1024; else if (memory_end 6*1024*1024) /* 否则如果内存6Mb,则设置缓冲区末端=2Mb */ buffer_memory_end = 2*1024*1024; else buffer_memory_end = 1*1024*1024; /* 否则则设置缓冲区末端=1Mb */ main_memory_start = buffer_memor
27、y_end; /* 主内存起始位置=缓冲区末端 */* 如果定义了内存虚拟盘,则初始化虚拟盘。此时主内存将减少。参见kernel/blk_drv/ramdisk.c。*/#ifdef RAMDISKmain_memory_start += rd_init(main_memory_start, RAMDISK*1024);#endifmem_init(main_memory_start,memory_end); /* 内核进行所有方面的初始化工作 */trap_init(); /* 陷阱门(硬件中断向量)初始化。(kernel/traps.c) */blk_dev_init(); /* 块设备初
28、始化。 (kernel/blk_drv/ll_rw_blk.c)*/chr_dev_init(); /* 字符设备初始化。 (kernel/chr_drv/tty_io.c)*/tty_init(); /* tty 初始化。 (kernel/chr_drv/tty_io.c)*/time_init(); /* 设置开机启动时间:startup_time */sched_init(); /* 调度程序初始化(加载了任务0 的tr,ldtr)(kernel/sched.c)*/buffer_init(buffer_memory_end); /* 缓冲管理初始化,建内存链表等。(fs/buffer.
29、c)*/hd_init(); /* 硬盘初始化。 (kernel/blk_drv/hd.c)*/floppy_init(); /* 软驱初始化。 (kernel/blk_drv/floppy.c)*/sti(); /* 所有初始化工作都做完了,开启中断 */* 下面过程通过在堆栈中设置的参数,利用中断返回指令启动任务0 执行 */move_to_user_mode(); /* 移到用户模式下执行。(include/asm/system.h)*/if (!fork() init(); /* 在新建的子进程(任务1)中执行 */main()流程图如图2.2: YN开始内存起始分配启动设备和程序初始
30、化开启中断切换到用户模式fork()!=0调用init()初始化调用pause ()运行任务0结 束图2.2 main()流程图2.5.3 pause()分析代码开始以任务0 的身份运行。对于任何其它的任务,pause()将意味着我们必须等待收到一个信号才会返回就绪运行态,但任务0(task0)是唯一的例外情况,因为任务0 在任何空闲时间里都会被激活(当没有其它任务在运行时),因此对于任务0 pause()仅意味着我们返回来查看是否有其它任务可以运行,如果没有的话我们就回到这里,一直循环执行pause()。pause()系统调用(kernel/sched.c,144)会把任务0 转换成可中断等
31、待状态,再执行调度函数。但是调度函数只要发现系统中没有其它任务可以运行时就会切换到任务0,而不依赖于任务0 的状态。2.5.4 static int printf(const char *fmt, .)分析产生格式化信息并输出到标准输出设备stdout(1),这里是指屏幕上显示。参数*fmt指定输出将采用的格式。该子程序正好是vsprintf 如何使用的一个例子。该程序使用vsprintf()将格式化的字符串放入printbuf 缓冲区,然后用write()将缓冲区的内容输出到标准设备(1-stdout)。static int printf(const char *fmt, .)va_list
32、 args;int i;va_start(args, fmt);write(1,printbuf,i=vsprintf(printbuf, fmt, args);va_end(args);return i;2.5.5 void init(void)分析 argv0中的字符“-”是传递给shell 程序sh 的一个标志。通过识别该标志,sh程序会作为登录shell 执行。其执行过程与在shell 提示符下执行sh 不太一样。static char * argv_rc = /bin/sh, NULL ; /* 调用执行程序时参数的字符串数组 */static char * envp_rc = HO
33、ME=/, NULL ; /* 调用执行程序时的环境字符串数组 */static char * argv = -/bin/sh,NULL ; /* 同上 */static char * envp = HOME=/usr/root, NULL ; 在main()中已经进行了系统初始化,包括内存管理、各种硬件设备和驱动程序。init()函数运行在任务0 第1 次创建的子进程(任务1)中。它首先对第一个将要执行的程序(shell)的环境进行初始化,然后加载该程序并执行之。setup(void *) &drive_info);/* 这是一个系统调用。用于读取硬盘参数包括分区表信息并加载虚拟盘(若存在的
34、话)和安装根文件系统设备。该函数对应函数是sys_setup() */然后以读写访问方式打开设备“/dev/tty0”,它对应终端控制台。由于这是第一次打开文件操作,因此产生的文件句柄号(文件描述符)肯定是0。该句柄是UNIX 类操作系统默认的控制台标准输入句柄stdin。这里把它以读和写的方式打开是为了复制产生标准 输出(写)句柄stdout 和标准出错输出句柄stderr。(void) open(/dev/tty0,O_RDWR,0);(void) dup(0); /* 复制句柄,产生句柄1 号 - stdout 标准输出设备 */(void) dup(0); /* 复制句柄,产生句柄2
35、号 - stderr 标准出错输出设备 */ 打印缓冲区块数和总字节数,每块1024 字节,以及主内存区空闲内存字节数。printf(%d buffers = %d bytes buffer spacenr,NR_BUFFERS,NR_BUFFERS*BLOCK_SIZE);printf(Free mem: %d bytesnr,memory_end-main_memory_start); fork()用于创建一个子进程(任务2)。对于被创建的子进程,fork()将返回0 值,对于原进程(父进程)则返回子进程的进程号pid。该子进程关闭了句柄0(stdin) 、以只读方式打开/etc/rc 文
36、件,并使用execve()函数将进程自身替换成/bin/sh 程序(即shell 程序),然后执行/bin/sh 程序。所带参数和环境变量分别由argv_rc 和envp_rc 数组给出。函数_exit()退出时的出错码1 操作未许可;2 - 文件或目录不存在。 if (!(pid=fork() close(0); if (open(/etc/rc,O_RDONLY,0) _exit(1); /* 如果打开文件失败,则退出(lib/_exit.c,10) */ execve(/bin/sh,argv_rc,envp_rc); /* 替换成/bin/sh 程序并执行 */ _exit(2); /
37、* 若execve()执行失败则退出 */ 下面还是父进程(1)执行的语句。wait()等待子进程停止或终止,返回值应是子进程的进程号(pid)。这三句的作用是父进程等待子进程的结束。&i 是存放返回状态信息的位置。如果wait()返回值不等于子进程号,则继续等待。if (pid0)while (pid != wait(&i) /* 空循环 */如果执行到这里,说明刚创建的子进程的执行已停止或终止了。下面循环中首先再创建一个子进程,如果出错,则显示“初始化程序创建子进程失败”信息并继续执行。对于所创建的子进程将关闭所有以前还遗留的句柄(stdin, stdout, stderr),新创建一个会
38、话并设置进程组号,然后重新打开/dev/tty0作为stdin,并复制成stdout 和stderr。再次执行系统解释程序/bin/sh。但这次执行所选用的参数和环境数组另选了一套。然后父进程再次运行wait()等待。如果子进程又停止了执行,则在标准输出上显示出错信息“子进程pid 停止了运行,返回码是i”,然后继续重试下去,形成一个死循环。 while (1) if (pid=fork()0是否等待?再次创建?打印创建出错打开执行pid= wait(&i)打印出错信息_exit(0)结 束图2.3 init()流程图第三章 内核调试3.1 运行环境内核编译运行于模拟Linux环境的Bochs
39、-2.1.1中。3.2 编译内核过程1) 用Bochs运行Linux0.11,开始如图3.1:图3.1 Bochs运行Linux0.112) 进入 /usr/src/Linux/init, 使用ls命令显示当前目录文件,可以看到我们需要的main.c文件,如图3.2:图3.2 ls命令显示当前目录文件3) vi main.c可以编译启动代码,insert插入,Esc+:wq保存并退出,如图3.3:图3.3 vi编译启动代码4) 返回Linux目录,使用make clean清除源代码生成的执行文件和中间的目标文件,如图3.4:图3.4 make clean5) 然后使用make命令编译生成新的内
40、核,如图3.5:图3.5 make命令编译生成新的内核6) 然后修复grub引导(这里不再赘述),重启后可看到多选菜单,默认首选就是我修改main.c编译的内核,如图3.6:图3.6 新建grub引导7) 进入后启动显示信息。首先,显示的是硬盘信息以及执行起始程序(kernel(),由此可见,启动时先要对硬件初始化和起始程序位置。然后显示硬盘是否有错误信息以及磁盘使用情况。因为LINUX编程有严格的限制,我试着将printf()定义移到main()之前,修改main()的内容并使之显示,没有成功,我就只修改了init()里的内容。可以看到显示打印出磁盘信息,然后用fork()创建一个进程,然后
41、打开/etc/rc/、执行bin/sh,由于没有出错,就没有跳转到出错的死循环中,没有错误信息显示,最后打印出创建进程成功。系统初始化完成,返回值OK。由此可以验证我们对初起代码main.c分析的正确性。如图3.7:图3.7第四章 总结与体会该程序首先确定如何分配使用系统物理内存,然后调用内核各部分的初始化函数分别对内存管理、中断处理、块设备和字符设备、进程管理以及硬盘和软盘硬件进行初始化处理。在内核源代码的init/目录中只有一个main.c 文件。系统在执行完boot/目录中的head.s 程序后就会将执行权交给main.c。该程序虽然不长,但却包括了内核初始化的所有工作。因此在阅读该程序的代码时需要参照很多其它程序中的初始化部分。而关于main.c,其中的头文件定义,需要引用到头文件等,因此要分析其数据结构,就需要查看Linux目录下的相关文件。main.c