启动过程:启动u-boot------>>启动linux内核----->>挂载根文件系统
什么是uboot:uboot是位启动linux内核前做的准备工作,是一种bootloader。bootloader的本质是一个裸机程序。
bootloader主要完成了哪些任务:1. 初始化异常向量表; 2. 初始化工作模式; 3. 初始化栈指针寄存器; 4. 初始化时钟; 5. 初始化串口等必要的外设。
还做了以下工作:1.关闭MMU(为了能访问真实内存); 2. 关闭DCache; 3. 初始化sdram; 4. 初始化nand flash; 5. 初始化网卡并集成一些必要的协议,如tcp,tftp等; 6. 提供一个类似于linux那样的终端,方便与操作人员互动; 7. 能够设置并保存一些参数; 8. 能够复制linux内核程序到指定的内存空间,并引导系统运行。
启动u-boot:
u-boot有两种启动方式:如下图所示:左右两个部分代表,当用OM[1:0]选择了不同的启动方式时,从2440角度看到的地址分布,关键是看左右两张图的最下面,即地址0x00000000出在选择不同方式启动时的差别:
1. 如果选择的是nor-flash启动(左图),那么此时nor-flash的首字节地址就是0x00000000,也就是说第一条指令从nor-flash取;
2. 如果选择的是nand-flash启动(右图),那么此时内部iram的首字节地址就是0x00000000,也就是说第一条指令要从内部iram取。如果是这种情况的话,2440在运行iram中的程序之前, 它会自动把nand-flash中的前4KB数据拷贝到iram去,这段程 序是2440固化好的,称之为“垫脚石”。
总结一下,那就是如果我们把u-boot写入到nor-flash中,并设置开发板以nor-flash启动,那么自然系统一上电就运行我们写 入在nor-flash中的u-boot了。事实上之前我们的裸机程序就是这么做的。
如果程序在nand-flash中,并设置开发板以nandflash启动,2440就会把nand-flash中最前面的4KB程序拷贝到 iram中执行。而这4KB程序一把都是做搬移工作的,因为uboot可能会大于4KB。
接下来我们采用的主要是第一种方式。
使用Jlink-flash:打开J-flash-----file-New project(已经创建好的工程s3c2440.jflash)--open data file(ubbot-bin经编译好的二进制文件)-----Target----connect-Target--Production Programming---
设置并打开minicom:sudo minicom -s:
选择 Serial port setup:
设置为如下格式:
然后保存(选择 Save setup as dfl),退出(exit)
再连接串口到虚拟机:
进入minicom:sudo minicom,
查看nanflash坏块信息:nand bad,
擦除整片nandflash:nand erase.chip ,会对坏块信息做出标记,运行的时候就把这部分跳过去了
启动linux内核
配置网络相关环境变量(环境 变量都是一些键值对。这里键值全是文本,或者简单认为就是 字符串。键值对中的键被称之为变量名,值被称之为变量的值)
u-boot提供了常用的网络协议,接下来我们用的最多的就是tftp协议。因为我们需要通过该协议从电脑上下载linux内核程序,但是要使用tftp,就需要我们配置好网络相关的环境变量。他们包括: 1. netmask子网掩码 2. ethaddr 以太网mac地址 3. serverip tftp服务器ip地址 4. ipaddr mini2440ip地址
需要注意的是,要统一ipaddr mini2440ip地址和serverip tftp服务器ip地址,这样启动程序的时候,就会自动跳转到服务器ip地址
sudo vi /etc/network/interfaces
添加网卡
注意:不能直接修改en33的值,因为这是一个wifi网卡,而连接虚拟机和开发板的是一个有线网卡,因此我们需要添加一块网卡。
点击虚拟机----设置---网络适配器----NAT模式----点击添加----网络适配器---设置为桥接模式---确认
再次用ifconfig查看,就可以看到新添加的网卡:ens36
配置网络:sudo vi/etc/network/interfaces ,并修改到如下格式(ens36由ifconfig命令查看)
重启网络:为了使配置文件生效,我们需要重启网络:sudo /etc/init.d/networking/restart
显示OK即配置成功
配置网卡
点击虚拟机左上角的编辑---虚拟网络编辑器----点击右下角更改设置
选择桥接模式---桥接至(自己的网络适配器 ,选择带有USB family controller的)
打开minicomPING网络
ping 192.168.1.100,ping成功则显示界面如下:
如何进行电脑与板子互传文件
查询服务器是否正常运行 sudo /etc/init.d/tftpd-hpa status
ubuntu下配置tftp服务器---安装tftp---->apt-get-install tftp-pha tftpd-hpa 这样服务器和客户端都装到ubuntu里面
配置tftp文件 ---> sudo vi .etc/default/tftpd-hpa
此时如果在/home.linux/tftpboot下写一个文件1.txt,就可以通过tftp将文件下载到板子里面,要将文件权限设置为最高chmod 777 1.txt
进入minicom下载1.txt进板子 tftp 0x30008000 1.txt
到目前我们已经在电脑上创建了tftp服务, u-boot也提供了tftp下载命令,这样我们就可以非常方便地从计算机上下载任何资源到开发板上了。事实上之后在开发阶段,我们都是通过tftp把linux内核下载到开发板的内存中,在引导linux启动的。
之所以这么做的目的是之后的驱动开发修改linux源码或者重新编译了Linux源码都需要重新把Linux烧写到nand-flash中去过于费时费力了。通常的做法是开发调试阶段就是通过tftp下载Linux内核,等一起调试停当,再固化到nand-flash中。
现在服务器上tftp服务目录中创建一个文件,然后再在开发板上用tftp命令现在这个文件,如: tftp 0x30008000 1.txt,将1.txt文件下载到开发板sdram地址为0x30008000处。之后使用md命令比对。
u-boot是一种引导程序,不要忘记我们最终的目的是要启动linux系统。既然u-boot可以使用tftp来下载任意文件,那么就可以使用tftp下载编译好的linux内核文件到mini2440中,并让mini2440引导linux系统。
1. 复制已经编译好的linux内核文件uImage到tftp根目录,如何编译出这个文件是之后我们要学习的重点;
2. 用tftp命令下载这个文件到开发板: tftp 0x30008000 uImage;
3. 下载完成后使用u-boot命令bootm 0x30008000启动linux操作系统;
如果之前整片擦除了nand-flash,在上一步启动linux系统以后就会发现系统启动到一半就停下来了。如果系统正常启动,并能够进入到终端的话,大概率是因为之前使用者配置了正确的根文件系统。
linux内核启动以后会自动去挂载根文件系统。那么什么是根文件系统?注意这里千万不要把根文件系统和fat,fat32,nfs……文件系统混为一谈。这里的根文件系统其实指的是一个文件夹,这个文件夹其实就是linux的系统根目录。
根文件系统是linux系统所不可或缺的,是linux内核启动以后要挂载的第一个文件系统。这个文件系统中包含有linux的各种命令,系统init进程等关键信息。
总结一下就是我们的工作已经进行到linux内核已经加载到内存中并且已经开始允许了,但是由于linux内核无法找到根文件系统,所以系统启动到一半就停了下来。很明显我们需要为linux系统提前准备好这个根文件系统,并通知linux系统去指定的地方去找到它。
首先第一个问题是这个根文件系统放哪?按照裸机, ubuntu的根文件系统是保存在硬盘上的,那么自然mini2440的根文件系统就应该在mini2440的硬盘nand-flash上。但是为了之后我们从电脑上向mini2440传递文件方便,在开发阶段我们一般都使用nfs,网络文件系统;
其次是这个这个根文件系统从何而来,总不能使用ubuntu拷贝一个吧?大小还在其次,关键是程序架构不同2440用不了。按照一般的方法,根文件系统的制作需要使用专门的工具软件busybox。其实就是创建一堆文件夹,并编译常用的命令进去。这里我们使用已经编译好的根文件系统rootfs111.tar。我们直接把这个压缩文件在nfs服务器文件夹中解压缩即可。
最后就是如何让mini2440知道我们的根文件系统是在nfs服务器上,并让linux自动去挂载呢?这个需要通过一个u-boot的环境变量来设置。
u-boot的bootargs环境变量是一个非常重要的环境变量,该环境变量并不是给u-boot自己用的,这个环境变量在bootm时传递给linux内核,使linux内核清楚应该去哪里找根文件系统一个设置nfs根文件系统的例子如下:
setenv bootargs ‘root=/dev/nfs nfsroot=192.168.1.100:/home/linux/nfs/rootfs ip=192.168.1.105 console=ttySAC0,115200 init=/linuxrc’
可以看出bootargs变量内部又是由若干键值对组成的,主要成员含义如下:
1. root=/dev/nfs 表示根文件系统是nfs即网络文件系统,如果使用nand-flash作为根文件系统载体,则root=/dev/mtdblockx
2. nfsroot=192.168.1.100:/home/linux/nfs/rootfs 表示网络文件系统挂载的具体位置
3. ip=192.168.1.105 linux启动以后的ip地址
4. console=ttySAC0,115200 linux系统启动以后控制台是串口0,波特率115200
5. init=/linuxrc linux系统启动后的第一个进程在根文件系统中的位置
以上我们学习了如何下载linux内核到开发板并引导系统去允许linux系统。这个过程如果每次都是手动操作的话过于繁琐,我们可以设置bootcmd环境变量来让系统自动执行这个过程
如果把上面的过程设置到bootcmd中去的话可以: setenv bootcmd ‘tftp 0x30008000 uImage;bootm 0x30008000’
可以看出bootcmd变量类似一个脚本,里面就放了两个命令,分别就是下载和启动命令。保存配置后重启开发板,启动后不要输入任何东西,系统在倒计时结束后自动运行bootcmd,如果已经进入u-boot命令行,可以用run bootcmd执行启动命令
之前的步骤中通过tftp下载了一个uImage文件,这个uImage文件就是linux内核程序。这个文件本身是通过编译linux内核源码得到的。三星公司同样为smdk2440定制了一套linux源码,友善之臂稍作修改后提供给我们,文件名:linux-2.6.32.2-mini2440-20150709.tgz
文件解压缩后如图,这些目录一起构成了linux源码,这些源码必须经过编译才能够在开发板上运行。同理要编译源码就要在编译前进行配置。
linux源码公板配置文件在arch/arm/configs下,理论上我们直接使用该目录下的s3c2410_defconfig文件就可以。但是由于mini2440在公板基础上做了一点修改,而修改后的配置文件就在linux源码顶层目录中,那些名为config_mini2440_xxx的文件都是。为什么有这么多?是因为mini2440适配的屏可能是lcd,还可能是vga,还不确定尺寸多大。所以搞出来一堆。这里最适合我们使用的是config_mini2440_td35
接下来问题是编译时怎么才能让makefile清楚我们要用config_mini2440_td35这个文件配置呢?其实虽然配置文件很多,但其实makefile只认一个配置文件: .config。我们可以用命令cp config_mini2440_td35 .config来设置.config为我们需要的配置文件
这里的python文件中有一处条件判断的错误,需要修改,第373行注释掉就行了(具体影响没有测试过)。
.config文件中配置内容非常多,所有驱动的、环境的、功能的、架构的配置都在这里,看起来非常不方便。好在linux社区提供了一种图形化方式来提高配置的可视化: make menuconfig
make menuconfig之后我们还要多次使用,退出前保存一下,如果不保存,编译时会出现很多选项让我们去选。
之后就是make uImage,经过漫长的等待之后编译输出Image arch/arm/boot/uImage is ready就表示编译完成
编译之后主要生成三个文件Image、 zImage、 uImage,而那个uImage文件就是我们之前下载用的那个,可以将新编译好的uImage下载到开发板中,尝试一下能否正常启动
理论上这个uImage是不能使用的,系统会出现复位,解决uImage不能使用的方法是打开arch/arm/boot/Makefile文件,找到第64行将STARTADDR改为0x30008040,再次编译重新下载之后问题就能解决。
之后需要把新make的uImage移动到tftpboot中(通过这个文件夹把uImage给开发板传过去,然后在minicom中使用tftp下载这个新的ulmage):
要解释这个问题出现的原因,我们要分析一下编译出的三个文件的区别。
Image:这个才是真正的镜像文件,大小约为4MB多一点
zImage:是Image压缩后的文件大小为2332580字节
uImage:也是Image压缩后的文件,不过它比zImage大了64字节,大小为2332644,多出的64字节是因为uImage中增加的一些文件信息,譬如linux版本,编译时间等等。很明显这些信息是不能够被执行的。问题在于之前编译好的内核我们下载到了0x30008000处,并且这个地址也是第一条指令的地址,称为入口地址。入口地址存放的不是arm指令,就造成了arm指令异常,产生复位。上面的修改将入口地址向后偏移了0x40-=64个字节,而这恰好就是正在的指令了,问题也就解决了。
到目前位置,一个linux操作系统已经在mini2440上运行起来了,结合交叉编译环境(arm-linux-gcc)和nfs等工具,我们可以在mini2440上编写任何我们在linux系统编程中学到的应用程序。
linux系统驱动程序分为三大类,字符设备驱动,块设备驱动和网络设备驱动。其中字符设备驱动是使用最多的一种,从点灯到IIC, SPI,音频设备等的驱动都是字符设备驱动。块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都给我们编写好了,大多数情况下都是直接可以使用的。所谓的块设备驱动就是存储器设备的驱动,比如 EMMC、NAND、 SD 卡和U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做块设备。网络设备驱动就更好理解了,就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。一个设备可以属于多种设备驱动类型,比如USB WIFI,由于其使用USB 接口,所以属于字符设备,但是其又能上网,所以也属于网络设备驱动。
接下来我们主要讨论如何编写字符设备驱动:
字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI、 LCD等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
在详细的学习字符设备驱动架构之前,我们先来简单的了解一下 Linux 下的应用程序是如何调用驱动程序的(驱动程序中的close()应该改为write(),应用程序中的close对应驱动程序中的release)
linux系统中万物皆文件,驱动程序加载后会在/dev目录下生成一个对应的文件,如/dev/led。应用程序就是先用open打开该文件,用write控制led的亮灭,用read读取led的亮灭,用完之后用close关闭该文件。
这里需要注意的是,应用程序运行在用户空间,驱动程序运行在内核空间。应用程序必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。一个open函数执行的过程如下:
很明显地,驱动程序必须也得有个open函数才行,那么怎么写这个open函数?写在哪里?如何编译就是我们接下来要学习地内容。
字符设备驱动程序的编写:
linux源码中字符设备驱动程序存放在driver/char目录下,我们也可以将我们自己的驱动程序保存在该目录下。
在driver/char下创建源文件first_driver.c并在文件中填入如下代码
linux驱动程序对代码有着特定的要求,首先就是驱动程序加载和卸载时的函数调用, 4行和11行分别就是当驱动程序加载和卸载时调用的函数。 17和18行分别用两个带参宏指出驱动程序初始化入口点和退出入口点。 19、 20表示该驱动程序遵守的协议和驱动程序的编写者。这样就完成了驱动程序最基础的框架。
程序写好了,如何编译呢?这里介绍两种方法:
第一种方法是告诉linux的Makefile我们添加了一个新的驱动程序,这种方法需要我们的驱动源码就放在driver/char目录中,恰好我们就是这么做的。步骤如下:
1. 打开 drivers/char/Kconfig 文件并添加如下内容:
Kconfig文件被称之为内核配置文件,这里我们添加了一个名为FIRST_DRIVER的配置选项,该配置选项为三态的,所谓三态是指将来的编译结果可以是模块,可以直接编译进内核还可以不编译。 default m是指默认编译成模块。最后那段是帮助文本。
之后我们运行make menuconfig,依次进入Device Driver->Character devices就可以看到My first driver了。选择该项按空格分别在编译成模块,编译进内核和不编译之间切换。我们选择’M’,编译成模块,以方便之后的调试,FIRST_DRIVER这里如图:第三张图的第一句就是这里要写的内容
2. 通过上一步,我们虽然可以在配置内核的时候进行选择,但实际上此时执行编译内核还是不能把first_dirver.c 编译进去的,还需要在 Makefile 中把内核配置选项和真正的源代码联系起来。
打开 drivers/char/Makefile并添加下面内容:
注意: CONFIG_之后的文字必须和Kconfig文件中配置选项名称一致,之后的文件名也必须和驱动源码文件名一致。
3. 之后在源码顶层目录下执行make modules就可以完成编译了,编译完成之后在driver/char目录下可以找到一个名为first_driver.ko文件,这个就是我们需要的驱动程序,将first_driver.ko通过nfs复制到开发板上,使用insmod xxx.ko命令加载驱动程序,使用lsmod查看已经加载的驱动,使用rmmod xxx卸载驱动程序,注意不用加.ko。