启动流程
pc机
BIOS(基本输入输出系统)
- 初始化时钟;初始化内存;基本硬件初始化
- 判断启动方式(usb 硬盘 光驱):区别系统存放位置和读取方式
引导程序
- 固化在硬盘(存储数据)最前面的部分
- 识别对应的操作系统(选择,默认第一个)
- 加载对应操作系统
操作系统(无图形化界面)
文件系统(NTFS/FAT(16/32)):规定数据如何组成为文件/文件夹,记录内存使用情况
应用程序(桌面图形化)
嵌入式
iRom code(厂家固化)
- 基本硬件配置(需要的时钟和设备)
- 判断启动方式(emmc USB ad卡)
- 将bootloader第一个阶段的代码加载到IRAM(芯片内部RAM)
IRAM中运行bootloader第一阶段(SPL)代码
- 初始化整个系统的时钟
- 初始化内存
- 将完整的bootloader搬运到内存中
内存中运行bootloader第二阶段代码
- 初始化开发版硬件设备(网卡,串口 usb)
- 加载对应操作系统(内存)
运行操作系统
挂载文件系统(EXT(2,3,4))
启动应用程序
问:为啥bootloader分两个阶段运行
答:芯片内部存储空间有限,如果内部空间做大,成本提高
环境搭建
交叉编译器(可执行)
不同架构涉及不同的编译器 arm-->arm-linux-gcc x86-->gcc
临时环境变量(当前终端有效):export PATH=$PATH:交叉编译器地址
永久环境变量
(当前用户有效):~/.bashrc配置文件中添加export PATH=$PATH:交叉编译器地址
(所以用户有效):/etc/bash.bashrcp配置文件添加export PATH=$PATH:交叉编译器地址
tftp 传输文件
开发板与pc进行文件传输===串口(速度过慢)==网络通信(tftp协议)
- 在linux中搭建对应环境
sudo apt-get install tftpd-hpa(服务器程序)
sudo apt-get install tftp-hpa(客户端程序)(用来进行对应测试) - 创建tftpdoot文件夹,保存对于tftp服务器文件
- 配置对于tftp的设置(文件夹路径)
vi /etc/default/tftp-hpa
TFTP_DIRTORY="路径"
权限“sudo chmod 777 -R 目录” - 重启tftp的服务
sudo service tftp-hpa resever - 测试:本都回环测试 tftp 127.0.0.1 get *.c quit
- 下载到内存 tftp 内存地址 文件名
uboot的参数(通过tftp下载对应的文件(镜像)到开发板)
print:打印当前的参数
setenvserverip(tftp服务器虚拟机ip)
setenvipaddr(开发板ip)
setenvgatewayip(网关的ip)
setenvethaddr(mac地址)
saveenv(保存对应参数到emmc)
测试:ping192.168.2.1(ping网关)
ping192.168.2.25(ping服务器)
tftp 0x41000000 uImage(下载linux镜像文件到内存(运行内存RAM))
tftp 0x43000000 ramdisk.img(下载文件系统镜像文件到内存(运行内存RAM))
tftp 0x42000000 exynos4412-fs4412.dtb(下载设备树文件到内存(运行内存RAM))
运行对应的Linux操作系统Linux操作系统(解析设备树文件,挂载文件系统)
bootm Linux镜像地址 文件系统地址 设备树的地址
bootcmd:设置开发板启动后,自动执行的命令
setenv bootcmd tftp 41000000 uImage\;tftp 42000000 exynos4412-fs4412.dtb\;tftp 43000000 ramdisk.img\;bootm 41000000 43000000 42000000
通过网络方式去挂载文件系统
bootcmd 没有下载文件系统
bootargs(uboot向linux内核传递的参数) 告诉操作系统去哪里挂载文件系统
U-boot bootargs参数:
bootcmd参数:
U-boot启动流程分析
- iRom code(厂家固化的代码) IRom
- 基本硬件配置(需要的时钟和设备)
- 判断启动方式(emmc USB ad卡)
- 将bootloader第一个阶段的代码(SPL)加载到IRAM(芯片内部RAM)
- BL1代码是由三星提供,irom的代码在读取BL1的时候,会对他做验证,如果验证通过就运行它
- SPL(U-boot第一阶段代码) IRam
- 设置为SVC特权模式
- 设置异常向量表,告诉内核异常向量表基址
- 关闭Cache(CPU内置高速小容量存储器)和MMU
- 初始化整个系统的时钟
- 初始化内存
- 将完整的bootloader搬运到内存中
- U-boot第二阶段代码 LPDDR
- 设置为SVC特权模式
- 设置异常向量表,告诉内核异常向量表基址
- 关闭Cache(CPU内置高速小容量存储器)和MMU
- 执行_main函数:设置栈指针sp,清理bss段,调用board_init_f
- arch/arm/lib/board.c (板子第一阶段初始化)
指针数组存储操作函数init_fnc_t *init_sequence[] = { /*获取cpu id*/ arch_cpu_init, /* basic arch cpu dependent setup */ /*定时器初始化*/ timer_init, /* initialize timer */ /*环境变量初始化:指定了一个默认的环境变量*/ env_init, /* initialize environment */ /*初始化波特率*/ init_baudrate, /* initialze baudrate settings */ /*初始化串口:确定哪一个串口设备输出(默认的输出是哪一个串口)*/ serial_init, /* serial communications setup */ /*控制台一级初始化*/ console_init_f, /* stage 1 init of console */ /*打印u_boot版本信息*/ display_banner, /* say that we are here */ /*打印cpu的信息*/ print_cpuinfo, /* display cpu info (and speed) */ /*记录内存的大小*/ dram_init, /* configure available RAM banks */ NULL, };为U-boot重定向做准备,在内存最顶端划分空间(后面要加载 zImage 内核、设备树 dtb,内核会占用 DDR 低地址区域)
重定向U-boot,防止被覆盖
清BSS段,准备运行第二阶段C语言代码
board_init_r(第二阶段初始化)
初始化标准输入输出设备stdio_init();
从存储介质中读取环境变量的值env_relocate();main_loop()
硬件信息
SOC :三星 exynos4412(4个cortex-A9 ARM核) 主频 1.4GHZ
board :origen<->fs4412
u-boot版本 :u-boot-2013.01
nfs(网络文件系统)
通过网络的方式进行映射把一台主机上文件映射到另一台主机上,它可以使用者访问网络上别处的文件就像在使用自己的计算机一样
- 搭建:sudo apt-get install nfs-kernel-server (nfs服务器)
- 解压对应的文件系统(rootfs.tar.gz)tar-xvf rootfs.tar·
- 修改对应的配置(/etc/export)nfs服务器可以被完成访问的目录
/home/linux/fs4412/rootfs*(rw,sync,no_root_squash)(路径) - 重启nfs的服务
sudo service nfs-kernel-server restart - 测试
找一块空目录
sudo mount 127.0.0.1:/home/linux/fs4412/rootfs 空目录的路径
创建一个文件,在源文件查看是否存在,如果存在,代表环境没有任何问题
解挂
sudo umount --lazy 空目录的路径
Linux操作系统编译
- arch —— 保存对应架构
arch/arm/configs ----- linux 是通用 我们需要进行对应的配置(芯片厂家配置)exynos_defconfig
arch/arm/boot/dts ----- 设备树信息
arch/arm/compressed ----- 自解压的代码
arch/arm/kernel/head.s ----- linux启动最开始的代码 - driver —— 驱动
- net —— 网络协议栈的代码
- 编译
- //导入默认配置
make ARCH=arm exynos_defconfig - //进行编译(编译架构)
make ARCH=arm CROSS_COMPILE=arm-linux-
- //导入默认配置
- 生成四个镜像文件
(最上面)vmlinux:.头部信息+Linux镜像文件
image:去掉头部的信息镜像文件
zImage:自解压的代码+去掉头部的信息镜像文件压缩
uImage(arch/arch/arm/compressed):自解压的代码+包含头部信息的Linux镜像文件压缩 - 生产uboot专属的镜像文件
make ARCH-arm CROSS_COMPILE-arm-linux- uImage
sudo apt-get installuboot-tools - 测试uImage
- 找不到文件系统
bootargs参数传递有没有问题
(虚拟机)nfs服务有没有生效
网络通不通
当前开发板(1inux镜像)要配置对应的nfs的功能 - 网卡驱动移植
我们Linux内核包含对应的网卡驱动
我们只需要选择配置
menuconfig====菜单====选择Linux镜像支持哪些功能
DM9000 - nfs
TCP/IP
内核级网络配置
nfs服务 - 屏蔽不需要的时钟信号
- 找不到文件系统
Image,zImage,uImage区别:
Image为zImage的内核的一种映像解压缩文件,为了节省系统容量,采用zImag文件(其开头自带解压代码),uImage为U-boot的专用映像文件,U-boot的bootm命令无法启动zImage,需要使用mkimage工具加上64字节的头文件(内核版本,加载位置,生成时间,大小等)
menuconfig ;.config ;Kconfig ;Makefile 关系
menuconfig:linux内核提供菜单选项(表示Linux内核支持哪些功能)(去餐馆里面的菜单)
.config:linux内核当前选择哪些功能 (点菜的凭证)
Kconfig:给Linux内核提供对应菜单选项 (当前餐馆所有厨师会做的菜)
Makefiie:管理Linux内核编译 (靠对应.config里面宏的配置)
Makefig:obj -$(宏)+=xxx.o
Kconfig:
名称:config <标识符>:标识符为省略"CONFIG_"的配置项
类型:<type> bool: y(静态编译)和n(不选择)
tristate: y、m(动态加载)、n
string: ASCII码串
hex: 16进制字符串
int: 10进制字符串
输出提示:prompt <提示符> [if <依赖表达式>]
数字范围:限制int/hex类型配置项 range(范围)
默认值:原.config中不存在本配置项,且用户未设置新值时,使用默认项 default(初始值)
依赖:决定配置项是否可见 depends on <值>:值可为宏或语法&&、||等
选择:依赖项被强制配置 select 配置选项名
帮助:---help---
帮助信息
source 路径 :包含该路径下所有Kconfig代码
设备树
包含当前开发板的设备信息(DM9000地址,led地址,蜂鸣器地址)
文件地址:arch/arm/boot/dts
三星:exynos4412-origen.dts母版 ---->exynos4412-fs4412.dts
.dts===源文件
.dtsi===头文件
.dtb===生成的文件(类似于可执行文件)
文件Makefile中加上对应设备树文件:exynos4412-fs4412.dtb \
在linux顶层的makefile去进行编译:make ARCH=arm CROSS_COMPILE=arm-linux- dtbs
添加对应的设备信息(DM9000)
文件系统
制作工具:
busybox ==== 文件系统(快速)
buildroot ==== 文件系统 linux内核 bootloader
yocto ====== 文件系统 linux内核 bootloader
linux启动过程分析
linux内核自解压
入口 start,保存参数,关中断
计算运行偏移,修复GOT:全局变量、函数地址
BSS 段清零:存放程序未初始化的全局变量、静态变量
初始化 C 运行环境,打开 Cache:设置栈,分配堆
安全检查后进行解压,搬移数据
关闭 cache,恢复机器 ID、atags,跳转到真正 vmlinux 内核入口执行
挂载根目录
执行init程序
init===linuxrc===bin/busybox===会去读取inittab,并加载和运行里面的内容
sysinit:在系统启动之后最先执行,只执行一次,init必须要等着结束之后在继续执行
wait:在sysinit进程执行完之后执行,只执行一次,init必须要等着结束之后在继续执行
once:在wait进程执行完之后执行,只执行一次,init不会等着结束之后在执行
respawn:在once进执行完之后执行,init进程一旦发现它死掉,会进行对应重启
askfirst:先打印press Enter to activate this console,按回车之后,才启动 shell 终端。
restart:在init进程收到SIGHUP信号后,重载 inittab 配置,执行 restart 对应的程序。
ctrlaltdel:组合键(ctrl+Alt+Del)重启
/etc/inittab
/etc/init.d/rcS
/etc/fsatb
配置自启动
- 在bootargs参数中指定自己的程序
init=/linuxrc------>init=自己的程序
缺点:无法启动init程序 - 默认启动init程序,修改/etc/inittab,使用sysinit,wait,once,respawn
- 修改/etc/init.d/rcS脚本文件,添加程序
例子:/hello& &:后台运行 - 修改/etc/profile脚本文件,添加程序(同3)
注意:
很多时候我们会选择在(2)或(4)中添加。
(2)可以让自己的进程死掉后,重新启动
(4)可以让自己的程序在执行的时候,执行的环境比较好。
制作ramdisk文件
- 制作镜像文件
dd if=/dev/zero of=ramdisk bs=1k count=8192(大小为8M) - 格式化这个镜像为ext2
mkfs.ext2 -F ramdisk - 拷贝对应文件系统文件到镜像
1.创建对应文件夹
mkdir FileSystem
2.挂载对应的ramdisk到新建的文件中
sudo mount ramdisk FileSystem
3.拷贝对应的文件到挂载目录
sudo cp ./fs4412/rootfs/* ./ -a - 卸载对应挂载
sudo umount FileSystem/ - 压缩ramdisk为ramdisk.gz
gzip --best -c ramdisk > ramdisk.gz - 将格式化为uboot识别的格式并拷贝到/tftpnoot下
mkimage -n "ramdisk" -A arm -O linux -T ramdisk -C gzip -d ramdisk.gz ramdisk.img
- A 指定CPU体系结构
- O 指定操作系统类型
- T 指定镜像类型
- C 指定镜像压缩方式
-a 指定镜像在内存中的加载地址,镜像下载到内存中时,要按照用mkimage制作镜像时,这 个参数所指定的地址值来下载。
-e 指定镜像运行的入口点地址,这个地址就是-a参数指定的值加上0x40。(因为前面有个 mkimage添加的0x40个字节的头)
-n 指定镜像名。
-d 指定制作镜像的源文件
cp ramdisk.img fs4412/tftpboot/
问:库是什么,动态库和静态库有什么区别,编译器如何链接库和操作系统如何知道加载库的路径
库:编译好的二进制目标代码集合,供其他程序使用
静态库.a:需要将库代码整个拷贝到可执行程序中,升级库:需要重新编译整个程序
动态库.so:只记录符号,不拷贝代码,程序运行的时候才加载库;更新库:直接替换 so 文件,程序不用重新编译
如何链接:-L:指定库的搜索路径(去哪里找.a/.so)-lxxx:指定链接libxxx.so或者libxxx.agcc 优先找动态库.so;想强制静态链接加‑static。
操作系统如何找到动态库路径:
/etc/ld.so.conf配置文件,写入库路径,执行ldconfig更新缓存。(最常用)- 环境变量
LD_LIBRARY_PATH,临时指定库搜索路径,终端生效。 - 可执行程序内部写死 rpath 路径(编译时
-Wl,-rpath=xxx)。 - 系统默认库目录
/lib/usr/lib。
问:如果让你写一个程序被杀死后,在自动启动起来,该如何做
- shell 脚本监控(/etc/init.d/rcS)
#!/bin/sh while true do ./myapp sleep 1 done - Linux 系统 init 进程守护(/etc/inittab)
::respawn - 父进程守护(PID不变)
while(1) { pid_t pid = fork(); if(pid == 0){ execl("./myapp","./myapp",NULL); exit(0); } wait(NULL); //等待子进程死亡 }
网卡移植
显示无网卡信息:net dm9000
查找报错代码:grep "Net:" * -nR
在include/configs/origen.h中添加宏定义:#define CONFIG_CMD_NET
显示网卡被跳过:grep "Net Initialization Skipped" * -nR
board_etc_init未实现(跳转发现该函数为一个弱函数)
搜索三星其他板子是怎么样实现这个代码的:grep "board_eth_init" * -nR | grep samsung
试着查看其他板子有没有使用dm9000网卡,以及使用方法:
将函数复制到board/samsung/origen/origen.c的endif后(不加宏编译,否则需要在origen.h里定义)
再次编译后发现并未出现.o文件,进当前目录的Makefile
在include/configs/origen.h定义宏:#define CONFIG_DRIVER_DM9000
再次编译,发现dm9000.data和dm9000.io未定义,再次在include/configs/origen.h
再次编译,无法ping通,再次加入宏定义,#define CONFIG_CMD_PING