文章目录
Linux根目录说明
文件类型说明
嵌入式Linux驱动开发总结一、环境的搭建
通常以Linux(Ubuntu)为开发环境,嵌入式Linux开发的环境主要包括:交叉编译工具链,但因为通常用的都是Windows主机,所以要么双系统,要么配虚拟机。
配虚拟机的话就须要打通Ubuntu与Windows之间的通道,例如文件的传送,我用的是FillZilla顾客端(ftp合同)。
交叉编译工具链交叉编译工具链是干嘛的?
在学习C语言时,我记得用的是codeblocks,当我们写好c程序后点击编译运行能够看见cmd命令窗口输出的结果。虽然在我们点击编译运行这个按键时,就相当于使用命令gcc来编译c程序使之成为可执行文件(在Windows下是exe格式,在Linux中是elf格式)。并且编译生成的可执行文件只能在此笔记本(我的时x86)上运行,不能在arm构架上运行,所以我们可以利用交叉编译工具在x86构架上编译生成可以在arm构架上运行的程序。
那为何不直接在arm构架上编译c文件呢?
第一:环境没有搭建好,例如开发板上连u-boot、Linux内核、跟文件系统都没有搭建好;
第二:即使环境搭建好了,而且也把交叉编译工具链放在了开发板上,并且因为开发办性能不是挺好,编译须要相对较长的时间。
在Ubuntu中用gcc编译的c文件,用file命令查看生成的可执行文件:
在Ubuntu中用交叉编译工具编译的c文件:
c文件是怎样被编译成可执行文件的
参看:一段C语言代码编译、运行全过程解析
$ gcc -E hello.c > hello.i //会生成预处理后的C源文件hello.i
$ gcc -S hello.i //将hello.i编译成汇编文件hello.s
$ gcc -c hello.s //将汇编文件hello.s汇编成hello.o
$ gcc hello.o -o hello //将目标文件链接成可执行文件hello
$ ./hello // 运行可执行文件hello
我们在Ubuntu中编撰一个c文件后,用gcc能够编译链接成可执行文件。但具体过程是如何的呢?由前面可知,须要预处理,编译,汇编,链接。编译时会去找头文件,去哪找呢?gcc工具链中(一个文件夹);编译时也须要库文件,去哪找呢?还是在gcc工具链中。
在Ubuntu中用whichgcc或whereisgcc可以找到gcc的路径,gcc命令是在/usr/bin下,而编译时所需的库文件在另外的地方,即/usr/lib中,/usr/include下是一些头文件。
ps.Linux中的usr是指UnixSystemResource,即Unix系统资源的简写。/usr是系统核心所在,包含了所有的共享文件。它是unix系统中最重要的目录之一,囊括了二补码文件,各类文档,各类头文件,还有各类库文件;还有众多程序,比如ftp,telnet等等。
而对于交叉编译来说,Ubuntu是没有的,须要我们自己安装。交叉编译工具链我(正-原-的教程)安装到了/usr/local/arm中去了。如下,
bin目录下是一些可执行文件,就是我们常用的交叉编译工具链,如arm-linux-gnueabihf-gcc,arm-linux-gnueabihf-objdump,arm-linux-gnueabihf-ld等。其它目录就是一些库文件和头文件,也包含一些可执行文件。
Linux中设置环境变量
若果没有设置环境变量的话,在某个shell中使用命令arm-linux-gnueabihf-gcc会报错,只有在交叉编译工具链的目录下才不会报错,解决方式就是将这一目录加入环境变量PATH中去。Linux下设置环境变量参请参考此
针对对所有用户都有效的方式:打开/etc/profile文件,在末尾添加如下内容:
exportPATH=$PATH:路径,如exportPATH=$PATH:/usr/local/arm/./bin
二、裸机开发方法回顾
不管哪些开发,最终都是操作底层硬件寄存器。
一、STC89C51
这是最能彰显直接操作寄存器的,例如初始化及配置并口时会直接操作相关寄存器。
void SerialInit()
{
TMOD=0x20;
TH1=0xf3; //波特率为4800
TL1=0xf3;
PCON=0x80; //SMOD为1,boundrate加倍
TR1=1;
SCON=0x50;
EA=1;
ES=1;
}
二、STM32
学习stm32时,最开始选择的库函数版本,当时还有寄存器版本,到如今又有了hal库。寄存器版本和c51差不多,直接操作寄存器;而库函数和hal库版本都是间接操作寄存器,即调用相关API对寄存器进行操作。下边是库函数版本:
u8 DS18B20_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//使能PORTA口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = DS18B20IO;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DS18B20PORT, &GPIO_InitStructure);
GPIO_SetBits(DS18B20PORT,DS18B20IO); //输出1
return DS18B20_Check();
}
三、嵌入式Linux下的开发
虽然在嵌入式Linux下开发的方法有好多,可以直接操作寄存器,也可以调用厂家的BSP包(相当于库函数),还可以采用框架(设备树、gpio和pinctrl子系统)等。整体过程如下:
一、裸机式开发
即直接操作寄存器,如下:
/* main.h文件部分内容
* 部分相关寄存器地址
*/
#define CCM_CCGR0 *((volatile unsigned int *)0X020C4068)
#define GPIO1_DR *((volatile unsigned int *)0X0209C000)
/* main.c文件部分内容
* 将GPIO1_DR的bit3清零
*/
void led_on(void)
{
GPIO1_DR &= ~(1<<3);
}
二、库函数式开发
即使用厂商提供的API,如下:
//bsp_clk.h头文件内容
#ifndef __BSP_CLK_H
#define __BSP_CLK_H
#include "imx6ul.h" //厂商写好的头文件
/* 函数声明 */
void clk_enable(void);
#endif
三、驱动框架下的开发
此开发方法涉及设备树、gpio子系统、pinctrl子系统、platform平台总线等。诸如可以直接在驱动程序中定义相关寄存器化学地址,之后编撰相应的驱动函数(如read、write等);也可以将寄存器化学地址表示在设备树中,在驱动中使用相关函数获取设备树中的寄存器化学地址。
哪些是设备树
设备树(DeviceTree),将这个词分开就是“设备”和“树”,描述设备树的文件称作DTS(DeviceTreeSource),这个DTS文件采用树状结构描述板级设备,也就是开发板上的设备信息,例如CPU数目、内存基地址、IIC插口上接了什么设备、SPI插口上接了什么设备等等。
对于设备树,需把握:
怎样通过设备树描述开发板硬件资源,即须要学习设备树的一些句型。怎样通过Linux内核解析设备树,即怎样获取设备树中相应设备的信息,例如寄存器化学地址,即须要把握常用的OF操作函数。明白设备树和驱动程序是怎样联系上去的,即设备结点的compatible属性。设备树在Linux系统中的彰显
设备树在Linux中是以文件及文件夹方式显示。
Linux内核解析DTB文件
Linux内核在启动的时侯会解析DTB文件,之后在/proc/device-tree目录下生成相应的设备树节点文件。
四、Linux三大鳄
假如学习过UCOS/FreeRTOS应当晓得,UCOS/FreeRTOS移植就是在官方的SDK包上面找一个和自己所使用的芯片一样的工程编译一下,之后下载到开发板就可以了。这么Linux的移植是不是也是这样的,下载Linux源码,之后找个和我们所使用的芯片一样的工程编译一下就可以了?很显著不是的!Linux的移植要复杂的多linux vi命令,在移植Linux之前我们须要先移植一个bootloader代码,这个bootloader代码用于启动Linux内核(相当于Windows下的BIOS),bootloader有好多,常用的就是U-Boot。移植好U-Boot之后再移植Linux内核,移植完Linux内核之后Linux还不能正常启动,还须要再移植一个根文件系统(rootfs),根文件系统上面包含了一些最常用的命令和文件。所以U-Boot、Linuxkernel和rootfs这两者一起构成了一个完整的Linux系统,一个可以正常使用、功能健全的Linux系统
一、u-boot
bootloader程序会先初始化DDR等外设linux 嵌入式 开发,之后将Linux内核从flash(NAND,NORFLASH,SD,MMC等)拷贝到DDR中,最后启动Linux内核。
u-boot可以看作是一个功能丰富、复杂的裸机程序,在编译生成可执行文件前可以依照自己的需求配置相应的功能,例如USB、网络、SD卡等。配置好后通过以下命令编译。
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12
怎样配置呢?
在uboot源码下的configs文件夹中,有许多配置文件,如下:
复制mx6ull_14x14_evk_emmc_defconfig,之后重命名为mx6ull_alientek_emmc_defconfig,更改其内容为:
CONFIG_SYS_EXTRA_OPTIONS="IMX_CONFIG=board/freescale/mx6ull_alientek_emmc/imximage.cfg,MX6ULL_EVK_EMMC_REWORK"
CONFIG_ARM=y
CONFIG_ARCH_MX6=y
CONFIG_TARGET_MX6ULL_ALIENTEK_EMMC=y
CONFIG_CMD_GPIO=y
在目录include/configs下添加ALPHA开发板对应的头文件,复制include/configs/mx6ullevk.h,并重命名为mx6ull_alientek_emmc.h。注意,头文件的头两行宏定义须要更改,按照头文件名更改,即#ifndefA,#defineA改为#ifndefB,#defineB。mx6ull_alientek_emmc.h中有好多宏定义,这种宏定义基本用于配置uboot,也有一些I.MX6ULL的配置项目。假如我们自己要想使能或则严禁uboot的个别功能,那就在mx6ull_alientek_emmc.h上面做更改即可。
uboot怎么启动Linux的
这个不用细究,真要用到时再研究。uboot中有两个特别重要的环境变量bootcmd和bootargs。
bootcmd保存着uboot默认命令,uboot倒计时结束之后才会执行bootcmd中的命令,这种命令通常都是拿来启动Linux内核的,例如读取EMMC或则NANDFlash中的Linux内核镜像文件和设备树文件到DRAM中,之后启动Linux内核。
bootargs保存着uboot传递给Linux内核的参数,例如bootargs=“console=ttymxc0,115200root=/dev/mmcblk1p2rootwaitrw”。
执行以下命令设置两个环境变量的值,之后直接输入boot即可启动Linux内核。
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;' //从mmc启动
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000' //从网络启动
saveenv
二、Linux内核
获取到源码后使用交叉编译工具链编译(由于我们是要移植到arm开发板起来)源码,命令如下:
#!/bin/sh
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16
编译完成之后才会在arch/arm/boot这个目录下生成一个称作zImage的文件,zImage就是我们要用的Linux镜像文件。另外也会在arch/arm/boo/dts下生成好多.dtb文件,这种.dtb就是设备树文件。
步入到目录/sys/bus/cpu/devices/cpu0/cpufreq中可以见到许多文件,这种文件是关于CPU频度等信息的。
三、根文件系统
Linux中的根文件系统更像是一个文件夹或则称作目录(在我看来就是一个文件夹,只不过是特殊的文件夹),在这个目录上面会有好多的子目录。根目录下和子目录中会有好多的文件,这种文件是Linux运行所必须的,例如库、常用的软件和命令、设备文件、配置文件等等。
根文件系统是Linux内核启动之后挂载(mount)的第一个文件系统,之后从根文件系统中读取初始化脚本,例如rcS,inittab等。根文件系统和Linux内核是分开的,单独的Linux内核是无法正常工作的,必需要搭配根文件系统。若果不提供根文件系统,Linux内核在启动的时侯才会提示内核崩溃(Kernelpanic)的提示。
根文件系统的这个“根”字就说明了这个文件系统的重要性,它是其他文件系统的根,没有这个“根”,其他的文件系统或则软件就别想工作。例如我们常用的ls、mv、ifconfig等命令显然就是一个个小软件,只是这种软件没有图形界面,并且须要输入命令来运行。那些小软件就保存在根文件系统中,这种小软件是如何来的呢?这个就是我们本章教程的目的,教你们来建立自己的根文件系统,这个根文件系统是满足Linux运行的最小根文件系统,后续我们可以按照自己的实际工作需求不断的去填充这个最小根文件系统,最终使其成为一个相对健全的根文件系统。
根文件系统的各个子目录/bin目录:
见到“bin”大家应当能想到bin文件,bin文件就是可执行文件。所以此目录下储存着系统须要的可执行文件,通常都是一些命令,例如ls、mv等命令。此目录下的命令所有的顾客都可以使用。/dev目录:
dev是device的简写,所以此目录下的文件都是和设备有关的,此目录下的文件都是设备文件。在Linux下一切皆文件,虽然是硬件设备,也是以文件的方式存在的,例如/dev/ttymxc0(I.MX6ULL根目录会有此文件)就表示I.MX6ULL的并口0,我们要想通过并口0发送或则接收数据就要操作文件/dev/ttymxc0,通过对文件/dev/ttymxc0的读写操作来实现并口0的数据收发。/etc目录:
此目录下储存着各类配置文件,你们可以步入Ubuntu的etc目录看一下linux 内核,上面的配置文件特别多!并且在嵌入式Linux下此目录会很简练。/lib目录:
lib是library的简称,也就是库的意思,因而此目录下储存着Linux所必须的库文件。这种库文件是共享库,命令和用户编撰的应用程序要使用这种库文件。/mnt目录:
临时挂载目录,通常是空目录,可以在此目录下创建空的子目录,例如/mnt/sd、/mnt/usb,这样就可以将SD卡或则U盘挂载到/mnt/sd或则/mnt/usb目录中。/proc目录:
此目录通常是空的,当Linux系统启动之后会将此目录作为proc文件系统的挂载点,proc是个虚拟文件系统,没有实际的储存设备。proc上面的文件都是临时存在的,通常拿来储存系统运行信息文件。/usr目录:
要注意,usr不是user的简写,而是UnixSoftware[System]Resource的简写,也就是Unix操作系统软件资源目录。这儿有个小知识点,那就是Linux通常被觉得是类Unix操作系统,苹果的MacOS也是类Unix操作系统。既然是软件资源目录,因而/usr目录下也储存着好多软件,通常系统安装完成之后此目录占用的空间最多。/var目录:
此目录储存一些可以改变的数据。/sbin目录:
此目录页用户储存一些可执行文件,并且此目录下的文件或则说命令只有管理员能够使用,主要用户系统管理。/sys目录:
系统启动之后此目录作为sysfs文件系统的挂载点,sysfs是一个类似于proc文件系统的特殊文件系统,sysfs也是基于ram的文件系统,也就是说它也没有实际的储存设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详尽的内核数据结构信息。/opt目录:
可选的文件、软件储存区,由用户选择将什么文件或软件放在此目录中。BusyBox建立根文件系统
得到BusyBox源码后,按须要进行配置(也可以通过可视化配置),之后编译(指定编译结果储存目录rootfs)完成后得到bin、sbin、usr三个目录,以及linuxrc这个文件。上面说过Linux内核init进程最后会查找用户空间的init程序,找到之后才会运行这个用户空间的init程序,因而切换到用户态。假如bootargs设置init=/linuxrc,这么linuxrc就是可以作为用户空间的init程序,所以用户态空间的init程序是busybox来世成的。
1、向rootfs的/lib目录添加库文件
Linux中的应用程序通常都是须要动态库的linux 嵌入式 开发,其实你也可以编译成静态的,并且静态的可执行文件会很大。假如编译为动态的话就须要动态库,所以我们须要先根文件系统中添加动态库,在rootfs中创建一个名为“lib”的文件夹。
lib文件夹创建好了,库文件从那里来呢?lib库文件从交叉编译器中获取,上面我们搭建交叉编译环境的时侯将交叉编译器储存到了“/usr/local/arm/”目录中。交叉编译器上面有好多的库文件,这种库文件具体是做哪些的我们作为初学者肯定不晓得,既然我不晓得那就简单粗鲁的把所有的库文件都放在我们的根文件系统中。
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
此目录下有好多的so(是转义)和.a文件,这种就是库文件,将此目录下所有的so*和.a文件都拷贝到rootfs/lib目录中。
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/lib
此目录下也有好多的的so和.a库文件,我们将其也拷贝到rootfs/lib目录中
2、向rootfs的usr/lib目录添加库文件
在rootfs的usr目录下创建一个名为lib的目录,将如下目录中的so和.a库文件都拷贝到rootfs/usr/lib目录中:
/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
3、创建/etc/init.d/rcS文件
rcS是个shell脚本,Linux内核启动之后须要启动一些服务,而rcS就是规定启动什么文件的脚本文件。在rootfs中创建/etc/init.d/rcS文件,之后在rcS中输入如下所示内容(创建好文件/etc/init.d/rcS之后一定要给其可执行权限!):
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH runlevel
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
4、创建/etc/fstab文件
在rootfs中创建/etc/fstab文件,fstab在Linux开机之后手动配置什么须要手动挂载的分区,格式如下:
<file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
5、创建/etc/inittab文件
inittab的详尽内容可以参考busybox下的文件examples/inittab。init程序会读取/etc/inittab这个文件,inittab由若干条指令组成。每条指令的结构都是一样的,由以“:”分隔的4个段组成,格式如下:
格式:<id>:<runlevels>:<action>:<process>
#etc/inittab
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/bin/umount -a -r 7 ::shutdown:/sbin/swapoff -a
至此,根文件系统基本建立完成。
根文件系统初步测试
测试方式就是使用NFS挂载,uboot上面的bootargs环境变量会设置“root”的值,所以我们将root的值改为NFS挂载即可。
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off' //设置 bootargs
saveenv //保存环境变量
软件运行测试
我们使用Linux的目的就是运行我们自己的软件,我们编译的应用软件通常都使用动态库,使用动态库的话应用软件容积就很小,而且得提供库文件,库文件我们早已添加到了根文件系统中。我们编撰一个小小的测试软件来测试一下库文件是否工作正常,在根文件系统下创建一个名为“drivers”的文件夹,之后我们学习Linux驱动的时侯就把所有的实验文件放在这个文件夹上面。
再Ubuntu下编撰hello,world文件,用交叉编译工具编译后拷贝到rootfs/drivers文件夹中去,之后在开发板上运行hello文件。
五、驱动开发
就是在Linux提供的框架下进行设备驱动的开发,主要分为:字符设备、块设备、网络设备,相关系统如下: