这几天一直在看有关linux内核和驱动程序设计的书,但总是看一点忘一点,每次都是这样,都快要崩溃了,所以索性把自己每天看的一点内容及时利用博文来记录下来,虽然以后看起来可能会觉得很一般,但是学习是一个需要逐步积累的过程,个人性子比较急,总想着一下就学完这些,但是越看越觉的linux内核是需要花费大量的时间来研究的!开题就先写到这儿吧,开始正文。本文参考自嵌入式linux基础教程第二版【本书已经获得图灵奖】。
一些基础概念:
设备树:目标板配置文件,包含了linux内核所需的用于引导目标板的信息。
闪存:内部无活动的部件,相对坚固稳定,只需要一种供电电压。一般根据其内部结构可以分为两种:分别是NOR型闪存和NAND型闪存。
NOR型闪存:
1.可以将一个比特从1改成0,但是如果需要将一个比特从0变为1,那么必须擦除整个块,这个需要浪费大量的时间
2.数据写入和擦除的方式(擦除块)
3.存储单元的写寿命
NAND型闪存
与NOR型闪存的区别:存储单元的内部架构不同。
4.提供了更小的块尺寸
5.串行访问
6.写寿命长
典型应用:
NOR型闪存:存储代码和数据,二进制可执行文件,并行,支持随机访问
NAND型闪存:以文件系统的格式来存储大容量的数据
闪存文件系统
以类似于硬盘驱动器组织数据的方式来管理闪存设备中的数据。
引导加载程序及配置
Linux内核
内存磁盘的文件系统镜像
更新空间
使用一种称为耗损均衡算法来延长闪存芯片的使用寿命。
JFFS2第二代日志闪存文件系统
Compact PCI
ACTA:(Advanced Telecommunications Computing Architecture)高级电信计算架构
Vmlinux:内核主体,与具体架构相关的可执行文件,而且符合ELF文件规范
System.map:帮助调试内核,基于文本的内核符号表
Kconfig:用于配置其所在目录的源码的特性
内核初始化步骤:
合成内核镜像的步骤:
1、vmlinux(ELF对象)内核主体
2、Objcopy生成Image二进制目标文件(去除符号、标记、注释的二进制镜像)
3、使用gzip压缩生成压缩后的二进制内核piggy.gz
4、Asm生成可启动的内核镜像文件zImage(具体情况,从高到低依次为piggy.o二进制内核镜像,misc.o,big_endian.o,head.o启动加载程序。
Bootloader:
引导加载程序是一种底层软件,存储在系统的非易失性内存(闪存或者ROM中),当系统加电时,它立刻获得控制权。
通常体积很小,主要用于底层的初始化,操作系统镜像的加载和系统诊断。
BIOS(Base Input Output System):基本输入输出系统,与Bootloader功能类似,只是BIOS一般用于X86 PC机上,而bootloader一般用于嵌入式系统中。
ARM引导时的控制流
1、加载引导加载程序
2、开始,启动加载程序(head.o)
3、加载内核镜像vmlinux(head.o)
4、执行内核的main.o文件,开始执行start_kernel,启动内核
启动加载程序(Bootstrap)不同于引导加载程序
启动加载程序作用如下:
创建合适的环境,解压并重新部署内核,并将控制权转交给内核,作为裸机引导加载程序和Linux内核之间的粘合剂。
Busybox是一个独立的二进制可执行程序,但支持很多常用的Linux命令行实用程序。
U-boot:一种通用的引导加载程序
设备树对象DTB
DTB是一个数据库,代表了一个给定板卡上的硬件元件,它由板卡/架构开发人员友情提供,并将其作为内核源码树的一部分,由IBM公司的OpenFirmware规范衍生而来,并且被选择作为一种默认的机制,用于将底层硬件信息从引导加载程序传递至内核。即就是它是作为一种在引导加载程序和内核之间传递底层硬件信息的方法。
GRUB:(GRand Unified Bootloader)统一引导加载程序,使用者:商业Linux发行版、拥有的特性有:
1、能够理解文件系统和内核镜像的格式
2、在引导时读取和修改其配置文件
3、支持从网络引导
LiloLinux加载器
桌面PC平台的商业Linux发行版
分区:是对物理设备的逻辑划分,而文件系统就存在于这个设备中,一个分区可以被看作是一个逻辑盘,它上面可以存储一个完整的文件系统。它由文件系统元数据和文件系统数据组成,文件系统元数据描述了块和文件之间的映射关系。
常见的分区类型有:Linux、FAT32、Linux Swap
Fdisk查看分区详细信息
Ext3是对ext2进行了扩展,添加了日志功能。
NFS(Network File System)网络文件系统
系统调用和API的区别与联系
API只是一个函数定义,说明了如何获得一个给定的服务,系统调用则是通过软中断向内核态发出一个明确的请求,按层次关系划分系统调用为底层,用户编程接口位上层,一个用户编程接口由0个或多个系统调用组成。
Sys_call_table系统调用表用于系统调用号与内核服务函数的映射
进程通信机制:管道、信号、信号量、消息队列等等
RAM的一部分被静态的划分给了内核,存放内核的代码和静态数据结构,RAM其他部分称为动态内存。
关于电脑启动和BIOS的整理
当电脑加电时,BIOS首先获得处理器的控制权,它的主要任务是初始化硬件,特别是内存子系统,并且从PC的硬盘驱动器中加载操作系统。
BIOS是复杂的系统配置软件,它拥有硬件架构的底层信息,在嵌入式系统中,使用Bootloader完成类似的功能。
当Linux内核掌握控制权时,引导加载程序就不存在了,Linux必须有一个文件系统,文件系统由一组预定义的目录和文件组成,这些目录和文件按照特定的布局存储在硬盘或者其他存储介质上,而Linux内核可以挂载这些介质作为其根文件系统。
MMU特点:当可用的物理内存的数量小于某个阈值时,将内存也面交换到大容量的存储介质中。
从软件的角度可以将嵌入式Linux系统划分为4个层次。
引导加载程序:包括固化在固件中的boot代码和Bootloader两大部分
内核:给具体类型开发板定制的内核及控制内核引导系统的参数
文件系统:包括根文件系统和建立于Flash内存的系统
用户应用程序:包括GUI、Web服务器、数据库、网络协议栈等。
根文件系统:挂载于文件系统层次结构根部的文件系统,简单的标记为/。包含应用程序和工具软件,通过他们来引导系统,初始化系统服务,加载设备驱动程序和挂载额外的文件系统。
/proc:虚拟文件系统,其中包含了系统信息
FHS<文件系统层次结构标准>
顶层目录项:
Bin:二进制可执行文件,系统的所有用户都可以使用。
Dev:设备节点
Etc:本地系统配置文件
Home:用户帐号文件
Lib:系统程序库,比如标准C程序库和很多其它的程序库
Sbin:二进制可执行程序,一般留给系统的超级用户使用
Tmp:临时文件
Usr:次级文件系统层次结构,用于存放应用程序,一般是只读的。
Var:包含一些易变的文件,比如系统日志和临时配置文件
Proc:虚拟文件系统,包含系统信息
Mnt:在层次结构中预留了一个位置,用于挂载用户设备和文件系统。
最小化文件系统
.
| -- bin
| |--busybox
| `--sh -> busybox
| -- dev
| `-- console
| -- etc
| `--init.d
|
`--rcS
` -- lib
| -- ld-2.3.2.so
| -- ld-linux.so.2 -> ld-2.3.2.so
| -- libc-2.3.2.so
`-- libc.so.6 -> libc-2.3.2.so
rcS:默认的初始化脚本,在系统启动时由busybox进行处理
Ld-2.3.2.so:Linux动态加载器
Libc-2.3.2.so:glibc,标准C程序库中的函数
Linux动态加载器负责将二进制程序加载到内存中,并且如果应用程序引用了共享库的函数,还需要执行动态链接。
Sbin/init是由引导时执行的第一个用户空间程序。
大多数应用程序有两类依赖关系:
动态链接的程序对程序库的依赖
可能需要的外部配置文件或数据文件
ldd 找出应用程序所依赖的函数库
Init是内核在完成引导过程之后创建的第一个用户空间进程,init是Linux系统中所有用户空间进程的最终父进程,此外,init提供了一组默认的环境变量参数,比如初始路径PATH。
Init的主要功能是根据一个特定的配置文件生成其他进程,配置文件是/etc/inittab。
Init为0时终止系统,为6时重启电脑,为5时,多用户配置,启动后进入图形界面。
与运行级别相关的脚本文件位于目录/etc/rc.d/init.d中,挂载早期根文件系统的机制。
初始RAM磁盘/initrd:用于启动早期用户空间处理流程。Initrd是一个功能完备的小型根文件系统,通常包含一些指令,用于在系统引导完成之前加载一些特定的设备驱动程序,一般用于加载访问真正的根文件系统必需的设备驱动程序。
ARM中使用了将initrd和内核拼接在一起,形成一个合成的镜像。
Linuxrc:当内核挂载初始的ramdisk时,会查找一个名为linuxrc的特殊文件,将该文件当成一个脚本文件,并执行其中包含的命令。
Initramfs:CPIO格式的档案文件。CPIO是用来建立、还原备份档的工具程序,它可以加入,解开CPIO或tar备份档内的文件。
Initrd:gzip压缩过得文件系统镜像
E2fsck:用于检查一个ext2文件系统的完整性。
/proc是一个接口,内核通过它可以获取一个linux系统所有运行进程的信息,现在可以提供更多的系统信息。
Cmdline条目中包含了用于启动这个进程的完整命令行。
Cwd和root分别指向系统的当前目录和根目录
Maps列出了分配给这个进程的每一段虚拟内存以及相关属性。
Sysfs是对具体的内核对象(比如物理设备)进行建模,并且提供一种将设备和设备驱动关联起来的方法。
Systool:浏览sysfs文件系统的目录结构
引导加载程序支持从其他一些非易失性存储设备中引导镜像,难点在于软件和硬件都相对比较复杂(比如硬盘驱动器),解决方法:
只支持硬件,从设备中的绝对扇区读取原始数据
一些比较有用的二进制工具:
Readelf -s
显示ELF镜像中的符号表
Readefl -e 发现和显示ELF镜像中的所有段。
-S 显示出ELF镜像中所有的段头部
.text 可执行代码段
.rodata常量数据
.data包含已初始化的全局数据,由C语言中程序库的启动代码使用,还可以包含应用程序中已初始化的数据成员。
.sdata 用于较小的已初始化的全局数据项,而且可以只存在于部分架构中。
.bss
.sbss:程序中未初始化的数据
objdump 与readelf很多功能是重叠的,然而它能够显示反汇编后的目标代码
objcopy 用于将一个二进制目标文件的内容复制到另一个文件中(对其进行格式化),而且在这个过程中,可能会转换目标文件的格式。当需要为存储在ROM或闪存中的镜像生成代码时,这个文件特别有用。
strip用于删除一个二进制文件中的符号和调试信息。
addr2line 找出一个可执行文件中的虚拟地址所对应的文件名和行号
strings 用于查看二进制文件中的ASCII码字符串数据。
ldd 列出一个文件或者多个目标文件所依赖的共享数据库
nm 用于显示目标文件中的符号,并提供了每个符号的属性,大写字母表示全局符号,小写表示局部符号。
B表示位于.bss段
T表示.text段
D表示.data段
A表示绝对地址,在后面的链接阶段不会再改变。
prelink对共享数据库和依赖它们的目标文件进行预链接,从而事先处理未解析的程序库引用。
数据显示调试器(data display debugger)是GDB和其他命令行调试器
strace是一个有用的追踪工具,能够捕捉一个Linux应用程序所执行的每个内核系统调用。
ltrace追踪程序库的调用
ps可以列出一个机器上所有运行的进程.
mtrace 能够分析应用程序调用malloc、realloc、free的情况
驻留内存大小是一个进程所使用的未交换的(实际驻留在内存中的)物理内存大小。
本文整理自嵌入式Linux基础教程
KDE:(K Desktop Environment)
GNOME:(GNU Network Object Model Environment)GNU网络对象模型环境
CTRL+ALT+F7组合回到GUI界面
从GUI回到命令行,CTRL+ALT+F1
Halt:注销和关闭系统
$ startx 启动GNOME桌面
Shell是一个命令解释器,在用户和操作系统之间提供面向行的交互式和非交互式接口。
Shell类型:Bourne Again Shell(bash)、Korn Shell(KS) 、TCSH Shell 以及 Z Shell。
Shell功能
接收命令行输入,解释后作为指令发送到操作系统
通过脚本文件,连续执行多个命令
多条命令之间可以用;分隔开来;例如ls;date
&&有条件的执行命令
历史命令可以输入history查看
标准输入<(从文件中获取数据,而不是从键盘获取【CTRL+D】终止)
管道 | 【一个命令的标准输出变成一个命令的标准输入】
数字0 标准输入 1 标准输出 2 标准错误
在后台运行作业,在命令后加 符号 &
例如ls mypicture &
符号链接是相同文件的不同名称,而符号链接的工作机制类似于引用其他的快捷键。
GTK+是用于GNOME应用程序的窗口部件集。
删除配置文件 .config 的命令
make mrproper
构建gcc交叉工具链
收集源代码
联编binutils(binary utilities二进制实用程序)提供了对二进制文件的底层处理功能,如链接、组合或解析ELF文件。
联编一个自举gcc
使用自举GCC联编glibc(GNU C标准库)
mkdir -p $srcdir//可以越级构建文件夹
MTD:Memory Technology Device内存技术设备。MTD子系统的目的是让内核支持种类繁多的类似内存的设备,是一个设备驱动程序,它提供了一套访问原始内存设备的通用API接口,MTD支持很多种闪存设备,然而,MTD不是块设备。
MTD与设备打交道是以擦除块为单位的,而块设备是以固定大小的块为操作单位的。
块设备有两种主要操作 ----------- 读取扇区和写入扇区,而MTD有3种:读、写、擦除。MTD设备的写寿命是有限的,所以MTD会包含内部逻辑将写操作分布开来,以延长设备寿命,这称为耗损均衡。
Busybox : 命令行工具
配置构建、安装busybox。
<1>执行一个配置工具
<2>运行make命令来构建这个软件包
<3>将编译出的二进制工具和一系列符号链接安装到你的目标系统中。
TFTP:轻量级的FTP(文件传输协议)
TFTP是以个TCP/IP服务,工作站必须开启这个服务,这就要求工作站能响应收到的TFTP请求报文,最简单的方法是运行一个TFTP服务器后台程序。
DHCP(动态主机配置协议)Dynamic Host Configuration Protocol)
BOOTP(启动协议)
DHCP服务器负责位DHCP或BOOTP客户端分配IP地址,我们需要位DHCP服务器配置一个IP子网,它分配的IP地址都来自于这个子网,同时,我们还需要配置客户段使用DHCP获取其IP地址。
DHCP服务器倾听来自DHCP客户端的请求,并为它分配地址和其他相关信息。
深入理解Linux内核笔记
1、区分物理地址、线性地址<虚拟地址> 逻辑地址
MMU 通过硬件电路,分段单元将逻辑地址转换为线性地址,接着被称为分页电路的硬件电路把线性地址转换成一个物理地址,逻辑地址由段标识符和段内偏移量组成。
段标识符:段选择符16位。
偏移量是32位长的字段。
Index:指定放在GDT或者LDT中的相应段描述符的入口
2、段选择符
索引号 TI RPL
TI : 表示指示器,放在LDT或者GDT(TI
= 0)
段寄存器:快速方便的找到段选择符
段寄存器:CS、DS、ES、FS、GS
CS寄存器含有一个两位的字段,指明CPU的当前特权级(Current
Privilege Level,CPL)
值为0表示最高优先级,为3表示最低优先级。
3、段描述符
8字节,放在全局描述符表(Global
Descriptor Table)或者局部描述符表(Local Descriptor Table)
通常只需定义一个GDT
利用一个增加的非编程寄存器来快速访问段描述符。
当将段选择符装入段寄存器时,相应的段描述符就由内存装入到对应的非编程寄存器,因为访问寄存器的速度比访存快得多
4、逻辑地址 -----> 线性地址【分段单元】
5、Linux GDT
单处理器只有一个GDT,而多处理器中每个CPU对应一个GDT,所有的GDT都存放在CPU_GDT_TABLE数组中,而所有GDT的大小和地址都存放在CPU_GDT_DESCR数组中。
每个GDT包含18个段描述符和14个空的,未使用的或保留的项.
调用门用于在调用预定义的函数时改变CPU的特权级。
6、分页单元
将线性地址划分为以固定长度为单位的组,称为页。
把所有的RAM分成固定长度的页框<物理页>
页框与页长度一致,页框是主存的一部分,是一个存储区域,而页只是一个数据块,可以存放在任何页框或磁盘中,把线性地址映射到物理地址的数据结构称为页表.页表存放在主存中,并在启用分页单元之前必须由内核对页表进行适当的初始化.
支持分页设置: 设置CR0寄存器的PG标志启用,当PG=0,线性地址被解释成物理地址.
每个活动进程必须有一个分配给它的页目录,物理地址存放在CR3寄存器中.
80X86分页
Directory
Table
offset
页的存取权限只有读和写两种.
PAE物理地址扩展(Physical Address Extension)
通过设置CR4控制寄存器中的物理地址扩展标志激活PAE硬件高速缓存.
硬件高速缓存:缩小CPU和RAM之间的速度不匹配.[CR0控制硬件高速缓存]
转换后援缓冲器:加快线性地址的转换
从2.6.11开始,分页采用了4级分页模型.
在初始化阶段,内核必须建立一个物理地址映射来指定哪些物理地址对内和可用,哪些不可用.
原因:<1>映射硬件设备I/O的共享内存
<2>相应的页框含有BIOS数据
保留页框有两种【绝不能被动态分配或交换到磁盘上】
<1>不可用的物理地址范围内的页框
<2>含有内核代码和已初始化的数据结构的页框
一般内核安装在0x100000开始处【即1M内存单元处】
页框0由BIOS使用,存放加电自检期间检查到的系统硬件配置[0~4KB]
从0x00000000到0x000FFFFF留给BIOS例程,并且映射ISA图形卡上的全部内存.[640KB~1MB]
外碎片:伙伴算法
内碎片:由于请求内存的大小与分配给它的大小不匹配
解决:<1>早期提供按几何分布的内存区大小
<2>SLAB分配器
进程的地址空间由允许进程使用的全部线性地址组成.
每个进程所看到的线性地址集合是不同的.一个进程所使用的地址与另一个进程所使用的地址没有什么关系.
内核通过线性区的资源表示线性地址区间.线性去是由起始线性地址,长度和一些访问权限来描述的.
内存描述符:与进程地址有关的全部信息.
程序以可执行文件的形式存放在磁盘中,既包括被执行函数的目标代码,也包括这些函数所使用的数据。
命令行参数和环境变量:用户可以影响程序执行方式的两种信息。
命令行参数:用户在shell提示符下紧跟文件名输入的信息
环境变量:<以PATH和HOME为例>从SHELL继承来的,但用户在装入并运行程序前可以修改任何环境变量。
在C与样中,main函数的标准形式为
Int main(int argc,char *argv[])
第3个可选参数是包含环境变量的参数,环境变量是用来定制进程的执行上下文,由此为用户或者其他进程提供通用的信息,或者允许进程在执行execve()系统调用的过程中保持一些信息
从硬盘启动:
硬盘的第一个扇区称为主引导记录【MBR】,该扇区中包括分区表和一个小程序。分区表:分区的起始扇区和处理它的操作系统类型
小程序:用来装载启动的操作系统所在分区的第一个扇区。
只有将内核镜像存放在活动分区中的操作系统才可以被启动。
ACPI(Advanced Configuration and Power Interface)高级配置与开机界面
POST(Power-On Self-Test)开机自检
PIC(Programmable Interrupt Controller)可编程中断控制器
POSIX(Portable Operating System based on unIX)基于UNIX的可移植操作系统
内核控制路径:表示内核处理系统调用,异常或者中断所执行的指令序列
可重入内核:意味这若干个进程,可以同时在内核态下执行。Linux下将所有Linux下将所有Linux进程分为4个段,分别为内核数据、代码段;用户数据、代码段。
为了对内核代码段寻址,只需将__KERNEL_CS宏产生的值装入CS寄存器即可。
扩展分页将4KB分页大小 --->4MB分页,用于把打断连续的线性地址转换成相应的物理地址。
PAE为2MB(页大小)
Linux采用的4级分页模型
页全局目录、页上级目录、页中间目录、页表。
对没有启用物理地址扩展的32位系统,两级已足够了,Linux通过将页上级、页中间目录位设为0实现,但为其保留了位置,具体的是将它们的项数设为1,并将其映射到页全局目录的一个适当的目录项而实现。
启用了物理地址扩展的32位系统使用了3级页表。
NUMA(非一致性内存访问)
给定CPU对不同内存单元的访问时间可能不太一样,理想情况下,任何种类的数据页都可以存放在任何页框中,无任何限制,但是实际的计算机体系结构有硬件制约,这限制了页框可以使用的方式。
80X86体系结构硬件约束:
ISA总线的直接内存存取(DMA)处理器有一个严格的限制:它们只能对RAM的前16MB寻址
在具有大容量RAM的现代32位计算机中,CPU不能直接访问所有的物理内存,因为线性地址的空间太小了。
为了解决上述限制,Linux将物理内存划分为3个管理区。
ZONE_DMA(包含低于16MB的内存页框)
ZONE_NORMAL(包含高于16MB而且低于896MB的内存页框)
ZONE_HIGHMEM(从896MB开始高于896MB的内存页框)
当用户态进程请求动态内存时,并没有获得请求的页框,而仅仅获得一个新的线性地址区间的使用权,二者以线性区就成为进程地址区间的一部分,这一区间叫做线性区。
系统调用参数传递
系统调用是一种横跨用户和内核两大陆地的特殊函数,所以既不能使用用户栈也不能使用内核栈,那么Linux是怎么处理的?
在发出系统调用之前,系统调用的参数被写入到CPU寄存器,然后在调用系统调用服务例程之前,内核再把存放在CPU中的参数拷贝到内核堆栈中,文件元数据是对文件进行描述的所有信息,但不包含文件的内容,例如:元数据包括文件的类型、文件的大小、文件拥有者的UID,访问权等
文件的所有元数据都存放在inode或索引节点中。
文件描述符是管理文件数据的一个内部核心数据结构,只有在进程访问文件时才可以获得文件描述符。
文件类型:
普通文件 在模式字段中的第一个字符由破折号-表示
目录文件 在模式字段中的第一个字符由d表示
块设备文件 在模式字段中的第一个字符由b表示
字符设备文件 在模式字段中的第一个字符由c表示
链接文件 在模式字段中的第一个字符由l表示
命名管道文件 在模式字段中的第一个字符由p表示
套接字文件 在模式字段中的第一个字符由s表示
进程模型通过定义执行上下文让多个任务可以并发执行。
执行上下文由对应程序的相关部分组成,包括程序的数据以及它能访问的内存地址空间,寄存器、栈及栈指针、程序计数器的值等。
进程创建:通过fork系统调用实现,当调用fork时,调用进程产生或分叉出一个新进程,新进程是子进程,而原进程是父进程,所有进程都有父进程,init除外。
Wait系统调用用来暂停父进程,直到子进程退出。
另一个进程替代当前进程用exec类系统调用实现。
进程id
进程0负责系统初始化,并创建进程1,进程1也叫init进程
Getpid()获得当前进程的PID。
Getppid()获得进程的父进程的PID
进程描述符 存放描述进程的所有信息
进程描述符:进程状态、PID、启动进程所用的命令等
虚拟终端:mingetty
串行终端:agetty
监控非活动终端,也就是监控等待用户发出登录请求的终端
快速浏览内核动态消息可以使用
Tail /var/log/messages
进程描述符:进程管理所需的特征,task_struct,用于存放进程的属性和信息,与进程相关的所有内核信息都存储在这个数据结构中
Current 当前运行进程的task_struct的引用
Task_struct详细信息
进程的属性:
与调度相关的字段
涉及进程件相互关系的字段
与进程信任状相关的字段