嵌入式基础

Posted llechee

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式基础相关的知识,希望对你有一定的参考价值。

文章目录


预览:

操作

``/etc/init.d/rcS` 是6ull开发板的开机脚本文件

echo "7 4 1 7" > /proc/sys/kernel/printk 开启内核打印信息


开发板挂载: mount -t nfs -o nolock,vers=3 192.168.2.125:/home/book/nfs_rootfs /mnt

NAT的话: mount -t nfs -o nolock,vers=3,port=2049,mountport=9999 192.x.x.x:/home/book/nfs_rootfs /mnt

mount命令用来挂载各种支持的文件系统协议到某个目录下。

mount成功之后,开发板在/mnt目录下读写文件时,实际上访问的就是Ubuntu中的/home/book/nfs_rootfs目录,所以开发板和Ubuntu之间通过NFS可以很方便地共享文件。

在开发过程中,在Ubuntu中编译好程序后放入/home/book/nfs_rootfs目录,开发板mount nfs后就可以直接使用/mnt下的文件。


网络

nmcli r wifi on
nmcli dev wifi
nmcli dev wifi connect "DRLyyds" password "19407010220" ifname wlan0

官方

移除GUI: mv /etc/init.d/S07hmi /root reboot
重新加载驱动
rmmod 8723bu.ko
modprobe 8723bu
移除其它控制
ps -ef | grep "wpa"
292 root     /usr/sbin/wpa_supplicant -u
kill -9 292
rm /etc/wpa_supplicant.conf
3.执行wifi链接操作
ifconfig wlan0 up
iw dev wlan0 scan |grep SSID
//wpa_passphrase  HUAWEI zihezihe  >> /etc/wpa_supplicant.conf
-/etc/wpa_supplicant.conf
ctrl_interface=/var/run/wpa_supplicant
ctrl_interface_group=0
update_config=1
network=
        ssid="DRLyyds"
        psk="19407010220"

-
wpa_supplicant -B -iwlan0 -c /etc/wpa_supplicant.conf
iw wlan0 link
udhcpc -i wlan0
ping -I wlan0 www.baidu.com

设置交叉编译工具链: vim ~/.bashrc 永久生效

export ARCH=arm
export CROSS_COMPILE=arm-buildroot-linux-gnueabihf-
export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin

一些命令

udhcpc : 获取ip 用网线的时候用

make之后, 可以使用make menuconfig来查看命令 状况 然后make 100ask_imx6ull_pro_ddr512m_systemV_qt5_defconfig make all

编译成功后文件输出路径为 output/images

buildroot2020.02.x 

  ├── output

​    ├── images  

​      ├── 100ask_imx6ull-14x14.dtb <--设备树文件  

​      ├── rootfs.ext2         <--ext2格式根文件系统

​      ├── rootfs.ext4 -> rootfs.ext2   <--ext2格式根文件系统 

​      ├── rootfs.tar         

​      ├── rootfs.tar.bz2       <--打包并压缩的根文件系统,用于NFSROOT启动

​      ├── 100ask-imx6ull-pro-512d-systemv-v1.img     <--完整的系统镜像(可以用来烧写emmc和sd卡)

​      ├── u-boot-dtb.imx       <--u-boot镜像

​      └── zImage         <--内核镜像


烧写裸机文件: imx是烧写EMMC, img是烧写到sdCard

parsec

入门

Linux启动流程

IMX6ULL启动流程

reset 开发板复位启动 -> rom 板子只读内存 ->加载UBOOT 去加载 第一段bootloader引导程序 -> uboot里有启动参数(环境变量)来启动kernel -> kernel + dtb 设备树 -> 启动Rootfs 跟文件系统 -> app

使用mount挂载

mount -t nfs -o nolock,vers=3 192.168.2.125:/home/book/nfs_rootfs /mnt

mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.2.125:/home/book/nfs_rootfs /mnt

挂载ubuntu的nfs目录到开发板/mnt目录下,挂载成功后使用df -h命令查看所有挂载。

编译内核镜像-设备树

编译内核模块->nfs挂载->传输到开发板 reboot即可使用新的 zImage镜像 *.dtb设备树 /lib/modules模块

编译UBOOT

make distclean 删除之前缓存 -> make mx6ull_14x14_evk_defconfig -> make

Linux

shell会去环境变量读取 可以通过echo SPATH 查看

ls: -l(long 显示完整信息) -a(显示隐藏文件) -h(列出大小)

cp: r:recursive,递归地,即复制所有文件 f:force,强制覆盖 d:如果源文件为链接文件,也只是把它作为链接文件复制过去,而不是复制实际文件

rm: r:recursive,递归地,即删除所有文件 f:force,强制删除

chgrp:改变文件所属用户组
        -R : 进行递归的持续更改,也连同子目录下的所有文件、目录都更新成为这个用户组之意。常常用在更改			某一目录内所有文件的情况。
chown:改变文件所有者
chmod:改变文件的权限
        r:  4或0
        ② w:  2或0
        ③ x:  1或0
        这3种权限的取值相加后,就是权限的数字表示。
        例如:文件a的权限为“-rwxrwx---”,它的数值表示为:
        ① owner = rwx = 4+2+1 = 7
        ② group = rwx = 4+2+1 = 7
        ③ others = --- = 0+0 +0 = 0
        使用u、g、o三个字母代表user、group、others 3中身份。此外a代表all,即所有身份。
        范例: 
        chmod u=rwx,go=rx  .bashrc

        也可以增加或去除某种权限,“+”表示添加权限,“-”表示去除权限:
        chmod a+w  .bashrc
        chmod a-x  .bashrc

查找\\搜索命令

find find 目录名 选项 查找条件 用来查找文件

例: find /home/book/dira/ -name " test1.txt "

! - 查找最近几天(几个小时)之内(之前)有变动的文件

$ find /home/book -mtime -2 //查找/home目录下两天内有变动的文件。

grep grep命令的作用是查找文件中符合条件的字符串,其格式如下: grep [选项] [查找模式] [文件名]

grep -rn "字符串" 文件名 r(recursive):递归查找 n(number):显示目标位置的行号 可以加入-w全字匹配。

可以在grep的结果中再次执行grep搜索,比如搜索包含有ABC的头文件,可执行如下命令:
$ grep  “ABC”  *  -nR  |  grep\\.h”
上述命令把第1个命令“grep  “ABC”  *  -nR”通过管道传给第2个命令。
即第2个命令在第1个命令的结果中搜索。

vi 学习见 -> 日常技巧

查找命令或应用程序的所在位置

which pwd //定位到/bin/pwd whereis pwd //可得到可执行程序的位置和手册页的位置

GCC

GCC 编译选项

常用选项描述
-E预处理,开发过程中想快速确定某个宏可以使用“-E -dM”
-c把预处理、编译、汇编都做了,但是不链接
-o指定输出文件
-I指定头文件目录
-L指定链接时库文件目录
-l指定链接哪一个库文件

其他有用的选项:

gcc -E main.c   // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c  > 1.txt  // 把所有的宏展开,存在1.txt里
gcc -Wp,-MD,abc.dep -c -o main.o main.c  // 生成依赖文件abc.dep,后面Makefile会用

echo 'main()'| gcc -E -v -  // 它会列出头文件目录、库目录(LIBRARY_PATH)

制作, 使用动态库 | 静态库

动态库:
制作、编译:
gcc -c -o main.o  main.c
gcc -c -o sub.o   sub.c
gcc -shared  -o libsub.so  sub.o  sub2.o  sub3.o(可以使用多个.o生成动态库)
gcc -o test main.o  -lsub  -L /libsub.so/所在目录/

运行:
① 先把libsub.so放到Ubuntu的/lib目录,然后就可以运行test程序。
② 如果不想把libsub.so放到/lib,也可以放在某个目录比如/a,然后如下执行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a  
./test

静态库:
gcc -c -o main.o  main.c
gcc -c -o sub.o   sub.c
ar  crs  libsub.a  sub.o  sub2.o  sub3.o(可以使用多个.o生成静态库)
gcc  -o  test  main.o  libsub.a  (如果.a不在当前目录下,需要指定它的绝对或相对路径)
运行:
不需要把静态库libsub.a放到板子上。
注意:执行arm-linux-gnueabihf-gcc -c -o sub.o   sub.c交叉编译需要在最后面加上 -fPIC参数。

GCC编译过程

  • (1)预处理

    C/C++源文件中,以“#”开头的命令被称为预处理命令,如包含命令“#include”、宏定义命令“#define”、条件编译命令“#if”、“#ifdef”等。预处理就是将要包含(include)的文件插入原文件中、将宏定义展开、根据条件编译命令选择要使用的代码,最后将这些东西输出到一个“.i”文件中等待进一步处理。

  • (2)编译

    编译就是把C/C++代码(比如上述的“.i”文件)“翻译”成汇编代码,所用到的工具为cc1(它的名字就是cc1,x86有自己的cc1命令,ARM板也有自己的cc1命令)。

  • (3)汇编

    汇编就是将第二步输出的汇编代码翻译成符合一定格式的机器代码,在Linux系统上一般表现为ELF目标文件(OBJ文件),用到的工具为as。x86有自己的as命令,ARM版也有自己的as命令,也可能是xxxx-as(比如arm-linux-as)。

    “反汇编”是指将机器代码转换为汇编代码,这在调试程序时常常用到。

  • (4)链接

    链接就是将上步生成的OBJ文件和系统库的OBJ文件、库文件链接起来,最终生成了可以在特定平台运行的可执行文件,用到的工具为ld或collect2。

编译过程常用选项:

常用选项描述
-E预处理,开发过程中想快速确定某个宏可以使用“-E -dM”
-c把预处理、编译、汇编都做了,但是不链接
-o指定输出文件
-I指定头文件目录
-L指定链接时库文件目录
-l指定链接哪一个库文件

总体选项

(1)-c

预处理、编译和汇编源文件,但是不作链接,编译器根据源文件生成OBJ文件。缺省情况下,GCC通过用’.o’替换源文件名的后缀’.c’,‘.i’,`.s’等,产生OBJ文件名。可以使用-o选项选择其他名字。GCC忽略-c选项后面任何无法识别的输入文件。

(2)-S

编译后即停止,不进行汇编。对于每个输入的非汇编语言文件,输出结果是汇编语言文件。缺省情况下,GCC通过用`.s’替换源文件名后缀 ‘.c’,'.i’等等,产生汇编文件名。可以使用-o选项选择其他名字。GCC忽略任何不需要汇编的输入文件。

(3)-E

预处理后即停止,不进行编译。预处理后的代码送往标准输出。

(4)-o file

指定输出文件为file。无论是预处理、编译、汇编还是链接,这个选项都可以使用。如果没有使用-o 选项,默认的输出结果是:可执行文件为a.out;修改输入文件的名称是source.suffix,则它的OBJ文件是source.o,汇编文件是 source.s,而预处理后的C源代码送往标准输出。

(5)-v

显示制作GCC工具自身时的配置命令;同时显示编译器驱动程序、预处理器、编译器的版本号。

Makefile

demo:

hello: hello.c
gcc -o hello hello.c
clean:
rm -f  hello

完善Makefile

第1个Makefile,简单粗暴,效率低:
test : main.c sub.c sub.h
	gcc -o test main.c sub.c

第2个Makefile,效率高,相似规则太多太啰嗦,不支持检测头文件:
test : main.o sub.o
	gcc -o test main.o sub.o
main.o : main.c
	gcc -c -o main.o  main.c
sub.o : sub.c
	gcc -c -o sub.o  sub.c	
clean:
	rm *.o test -f

第3个Makefile,效率高,精炼,不支持检测头文件:
test : main.o sub.o
	gcc -o test main.o sub.o
%.o : %.c //%是通配符
	gcc -c -o $@  $<
clean:
	rm *.o test -f

第4个Makefile,效率高,精炼,支持检测头文件(但是需要手工添加头文件规则):
test : main.o sub.o
	gcc -o test main.o sub.o
%.o : %.c 
	gcc -c -o $@(代表目标文件)  $<(代表第一个依赖)
sub.o : sub.h
clean:
	rm *.o test -f

第5个Makefile,效率高,精炼,支持自动检测头文件:
objs := main.o sub.o
test : $(objs)
	gcc -o test $^(表示所有的依赖)
# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files(依赖文件) := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))

# 把依赖文件包含进来
ifneq ($(dep_files),) (如果这个变量不等于空)
  include $(dep_files)
endif
%.o : %.c
	gcc -Wp,-MD,.$@.d  -c -o $@  $<
clean:
	rm *.o test -f
distclean:
	rm  $(dep_files) *.o test -f
	
----
CFLAGS(编译参数) = -Werr (把所有警告都当作错误) -I(指定头文件目录) . -Iinclude(默认头文件目录)

**假想目标 : ** .PHONY

**即时变量 延时变量 : ** export A := xxx 定义时就确定 B = xxx 使用时才确定

?= 延时变量 只有在第一次定义才起效 += 是即时还是延时取决于前面的定义

函数

目标(target)…: 依赖(prerequiries)…
<tab>命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的2个条件:依赖文件比目标文件新,或是 目标文件还没生成。

$(foreach var,list,text)

对list中的每一个元素,取出来赋给var,然后把var改为text所描述的形式。

例子:
objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d

$(wildcard pattern)

pattern所列出的文件是否存在,把存在的文件都列出来。

例子:
src_files := $( wildcard  *.c)  // 最终 src_files中列出了当前目录下的所有.c文件

$(filter pattern..., text) $(filter-out pattern..., text)

在text中取出符合patten格式的值 (不符合)

$(patsubst pattern, replacement, $(var))

从列表中取出每一个值, 如果符合pattern, 则替换为replacement

gcc -M c.c 打印出依赖

gcc -M -MF c.d c.c 把依赖写入文件c.d

gcc -c -o c.o c.c -MD -MF c.d 编译c.o, 把依赖写入文件c.d

通用Makefile使用

目录: D:\\imx6ull\\01_all_series_quickstart\\04_嵌入式Linux应用开发基础知识\\source\\05_general_Makefile\\Makefile_and_readme

待补充

文件IO

  • 如果要访问真实的文件(SD卡 等等) 需要挂载

    可以 cat /proc/mounts 看是否自动挂载

    使用mount /dev/sda1 /mnt 来手动挂载到mnt目录下

  • 有些文件是虚拟的,Linux内核提供虚拟文件系统,根据虚拟文件系统里面的文件可以查看内核的一些信息/sys

  • 其他为驱动文件, 通过函数去直接操作硬件

在Linux内核有俩种驱动,字符设备驱动’c’(ls -al 第一个字母) 块设备’b’ 主设备号 次设备号 (通过这些来确定到底是哪一个驱动 哪一个硬件)

不是通用的函数:ioctl/mmap

俩个复制文件的demo:

这个是用 write / read 方式
    
02 #include <sys/types.h>
03 #include <sys/stat.h>
04 #include <fcntl.h>
05 #include <unistd.h>
06 #include <stdio.h>
07
08 /*
09  * ./copy 1.txt 2.txt
10  * argc    = 3
11  * argv[0] = "./copy"
12  * argv[1] = "1.txt"
13  * argv[2] = "2.txt"
14  */
15 int main(int argc, char **argv)
16 
17      int fd_old, fd_new;
18      char buf[1024];
19      int len;
20
21      /* 1. 判断参数 */
22      if (argc != 3)
23      
24              printf("Usage: %s <old-file> <new-file>\\n", argv[0]);
25              return -1;
26      
27
28      /* 2. 打开老文件 */
29      fd_old = open(argv[1], O_RDONLY);
30      if (fd_old == -1)
31      
32              printf("can not open file %s\\n", argv[1]);
33              return -1;
34      
35
36      /* 3. 创建新文件 */
37      fd_new = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
38      if (fd_new == -1)
39      
40              printf("can not creat file %s\\n", argv[2]);
41              return -1;
42      
43
44      /* 4. 循环: 读老文件-写新文件 */
45      while ((len = read(fd_old, buf, 1024)) > 0)
46      
47              if (write(fd_new, buf, len) != len)
48              
49                      printf("can not write %s\\n", argv[2]);
50                      return -1;
51              
52      
53
54      /* 5. 关闭文件 */
55      close(fd_old);
56      close(fd_new);
57
58      return 0;
59 


这个使用 mmap方式

02 #include <sys/types.h>
03 #include <sys/stat.h>
04 #include <fcntl.h>
05 #include <unistd.h>
06 #include <stdio.h>
07 #include <sys/mman.h>
08
09 /*
10  * ./copy 1.txt 2.txt
11  * argc    = 3
12  * argv[0] = "./copy"
13  * argv[1] = "1.txt"
14  * argv[2] = "2.txt"
15  */
16 int main(int argc, char **argv)
17 
18      int fd_old, fd_new;
19      struct stat stat;
20      char *buf;
21
22      /* 1. 判断参数 */
23      if (argc != 3)
24      
25              printf("Usage: %s <old-file> <new-file>\\n", argv[0]);
26              return -1;
27      
28
29      /* 2. 打开老文件 */
30      fd_old = open(argv[1], O_RDONLY);
31      if (fd_old == -1)
32      
33              printf("can not open file %s\\n", argv[1]);
34              return -1;
35      
36
37      /* 3. 确定老文件的大小 */
38      if (fstat(fd_old, &stat) == -1)
39      
40              printf("can not get stat of file %s\\n", argv[1]);
41              return -1;
42      
43
44      /* 4. 映射老文件 */
45      buf = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd_old, 0);
46      if (buf == MAP_FAILED)
47      
48              printf("can not mmap file %s\\n", argv[1]);
49              return -1;
50      
51
52      /* 5. 创建新文件 */
53      fd_new = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
54      if (fd_new == -1)
55      
56              printf("can not creat file %s\\n", argv[2]);
57              return -1;
58      
59
60      /* 6. 写新文件 */
61      if (write(fd_new, buf, stat.st_size) != stat.st_size)
62      
63              printf("can not write %s\\n", argv[2]);
64              return -1;
65      
66
67      /* 5. 关闭文件 */
68      close(fd_old);
69      close(fd_new);
70
71      return 0;
72 

系统调用函数怎么进入内核?

应用程序通过 open/read/write 进而去访问设备、文件。

进入到内核kernel, 对于普通文件,会使用文件系统去读取对应的磁盘, 对于硬件, 会去找到内核中对应的驱动程序调用相关的程序

调用过程

这些关于文件的函数一般是由glibc提供的,以及其他不同版本的C库(ucibc), open这些要去打开文件需要依赖操作系统提供的功能(怎么进入Linux内核), 可以把内核当作另外一个APP , 这个app不能去调用另外一个app里面的函数

open函数里面会放一条指令, 比如在32为cpu,会使用swi指令, 对于64位cpu, 使用svc指令, 一旦执行这些指令, 就会触发CPU的异常, 会导致CPU跳到某个地址里面去执行系统里面的代码, 这时候操作系统就会去执行对应的sys_open sys_read函数, 对于read , write 函数也是如此.

那么它怎么知道app触发这个异常是为了去调用sys_open或者是sys_read, glibc在实现sys这些系统调用的时候, 会使用这些命令(swi等)来触发异常, 并且传入不同的参数给内核, 内核根据不同的参数来分辨是想调用哪个

  • glibc里面怎么传递参数给内核呢? 最开始用的old abi 二进制接口(老的API), 在执行swi指令的时候可以在这条指令后面跟一个数值, 内核执行这个swi的异常处理函数时会把后面的数值取出来, 根据里面的值知道要去调用什么函数.
  • 后来又有改进,使用EABI, 不在swi里面传参数了, 它事先在 R7 寄存器(汇编) 里面存入那些值, 当发生异常的时候,内核会去R7寄存器得到这些值, 进而得到去执行sys的什么函数
  • 对于64位,使用svc指令, 通过X8寄存器传入, 内核里面有一个系统函数调用指针数组, sys_call_table内核把库函数传进来的值处理之后, 用这个值作为下标, 在这个数组里面对应的函数.(阅读glibc的源码,内核源码)

app调用openread导致内核的sys_openread被调用,那么内核的sys_openread会做什么事情呢

Linux软件架构

在linux系统软件架构可以分为4个层次(从低到高分别为):

1.引导加载程序

​ 引导加载程序(Bootloader)是固化在硬件Flash中的一段引导代码,用于完成硬件的一些基本配置,引导内核启动。

​ 同时,Bootloader会在自身与内核分区之间存放一些可设置的参数(Boot parameters),比如IP地址,串口波特率,要传递给内核的命令行参数。

2.系统内核

​ 系统内核(Kernel)是整个操作系统的最底层,它负责整个硬件的驱动,以及提供各种系统所需的核心功能,包括防火墙机制、是否支持LVM或Quota等文件系统等等,如果内核不认识某个最新的硬件,那么硬件也就无法被驱动,你也就无法使用该硬件。计算机真正工作的东西其实是硬件,例如数值运算要使用到CPU、数据储存要使用到硬盘、图形显示会用到显示适配器、音乐发声要有音效芯片、连接Internet 可能需要网络卡等等。内核就是控制这些芯片如何工作。

3.文件系统

​ Linux文件系统(File System)中的文件是数据的集合,文件系统不仅包含着文件中的数据而且还有文件系统的结构,所有Linux 用户和程序看到的文件、目录、软连接及文件保护信息等都存储在其中。

​ 文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统。

4.用户程序

​ 用户应用程序(Application)为了完成某项或某几项特定任务而被开发运行于操作系统之上的计算机程序。

Linux启动过程

正常启动过程中,Bootloader首先运行,然后将内核复制到内存中(或者在固态存储设备上直接运行,但是效率较低),并在内存某个固定的地址(包括地址与参数的结构)设置好要传递给内核的参数,最后运行内核。内核启动后,挂载(mount)根文件系统(Root filesystem),启动文件系统中的应用程序。

上电 ——> Bootloader —[传递参数]—> 加载内核 ——> 内核挂载根文件系统 ——>执行应用程序

如何理解Bootloader与Kernel

操作系统内核本身就是一个裸机程序,和我们学的uboot和其他裸机程序没有本质的区别;事实上,不少U-Boot源码就是根据相应的Linux内核源程序进行简化而形成的,尤其是一些设备的驱动程序。如果我们去琢磨U-Boot源码的注释,便会轻易的发现这一情况。

区别就是操作系统运行起来后可以分为应用层(用户态)和内核层(内核态),分层后,两层的权限不同(实现的原理是基于CPU的模式切换),内存访问和设备操作的管理上更加精细(内核可以随便方位各种硬件,而应用程序只能被限制的访问硬件和内存地址)。

以ARM处理器为例,除用户模式外,其余6种工作模式都属于特权模式:

用户模式(USR):正常程序执行模式,不能直接切换到其他模式

系统模式(SYS):运行操作系统的特权任务,与用户模式类似,但具有可以直接切换到其他模式等特权

快中断模式(FIQ):支持高速数据传输及通道处理,FIQ异常响应时进入此模式

中断模式(IRQ):用于通用中断处理,IRQ异常响应时进入此模式

管理模式(SVC):操作系统保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发)

中止模式(ABT):用于支持虚拟内存和/或存储器保护,在ARM7TDMI没有大用处

未定义模式(UND):支持硬件协处理器的软件仿真,未定义指令异常响应时进入此模式

Linux内核态是从ARM的SVC即管理模式下启动的,但在某些情况下、如:硬件中断、程序异常(被动)等情况下进入ARM的其他特权模式,这时仍然可以进入内核态(因为就是可以操作内核了);同样,Linux用户态是从ARM用户模式启动的,但当进入ARM系统模式时、仍然可以操作Linux用户态程序(进入用户态,如init进程的启动过程)。

即:Linux内核从ARM的SVC模式下启动,但内核态不仅仅指ARM的SVC模式(还包括可以访问内核空间的所有ARM模式);Linux用户程序从ARM的用户模式启动,但用户态不仅仅指ARM的用户模式。

直观来看:uboot的镜像是u-boot.bin,Linux系统的镜像是zImage,这两个东西其实都是裸机程序镜像。从系统启动的角度来讲,内核和uboot都是裸机程序。

文件系统

概念

Linux文件系统中的文件是数据的集合,文件系统不仅包含着文件中的数据而且还有文件系统的结构,所有Linux 用户和程序看到的文件、目录、软连接及文件保护信息等都存储在其中。这种机制有利于用户和操作系统的交互。

尽管内核是 Linux 的核心,但文件却是用户与操作系统交互所采用的主要工具。这对 Linux 来说尤其如此,这是因为在 UNIX 传统中,它使用文件 I/O 机制管理硬件设备和数据文件

虚拟文件系统、根文件系统和文件系统

VFS:

Linux支持多种文件系统类型,因为它将底层与应用层分隔开;而提供统一的接口支持应用层对于不同实现的文件系统的访问,这个统一的接口称为虚拟文件系统VFS。

kernel中以VFS去支持各种文件系统,如yaffs,ext3,cramfs等等。yaffs/yaffs2是专为嵌入式系统使用NAND型闪存而设计的一种日志型文件系统。在内核中以VFS来屏蔽各种文件系统的接口不同,以VFS向kernel提供一个统一的接口。

根文件系统

文件系统指文件存在的物理空间,linux系统中每个分区都是一个文件系统,都有自己的目录层次结构。以“/”为顶级目录的文件系统称为根文件系统。

Linux启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。系统正常挂载根文件系统之后可以自动或手动挂载其他的文件系统(根文件系统是其他文件的最终挂载点)。因此,一个系统中可以同时存在不同的文件系统。

也就是说:

根文件系统可以是任何kernel支持的文件系统类型(ext4,yaffs等)。

但它必须包含linux内核启动时所必需的文件(根文件系统必需存在的目录 /dev /bin /sbin 等等),不然系统启动会失败。

根文件系统是之所以有个根(/)字,是因为它是linux系统启动时挂载(mount,所谓挂载:就是在内存中创建一个虚拟的文件对应具体的存储空间分区的过程,挂载时不会保存信息,下次启动时得重新挂载)的第一个文件系统,启动完成后可以自动(配置etc/fstab)或者手动的方式将其它文件系统(分区)挂载到根文件系统中。

其他文件系统

不同的文件系统类型有不同的特点,因而根据存储设备的硬件特性、系统需求等有不同的应用场合。在嵌入式Linux应用中,主要的存储设备为 RAM(DRAM, SDRAM)和ROM(常采用FLASH存储器),常用的基于存储设备的文件系统类型包括:jffs2, yaffs, cramfs, romfs, ramdisk,initramfs, ramfs/tmpfs,ubifs等。

uboot与根文件系统的关系

早期的uboot没有分区的概念,uboot只知道应该将什么数据烧写到存储介质的什么区间中。

(也就是说,对于uboot来看,只有起始地址结束地址等,A~B地址放内核,C~D地址放文件系统,即:规定哪个地址区间放内核或者文件系统等)

虽然此后,uboot中渐渐也有了MTD等管理分区部分的功能。尽管如此,不影响我们的学习理解。

总结

cpu首先执行位于0地址的uboot,uboot启动以后初始化一些资源,告诉内核有关的参数并引导内核,内核通过事先添加对于某种文件系统类型的支持驱动(相当于一小段程序),读取uboot等boot loader在指定的区域烧写制作好的文件系统镜像,内核解析并挂载成根文件系统,并在此基础上,通过VFS再挂载不同的文件系统类型,完成启动以后,再去管理有关的资源(包括应用程序)

应用编程

Framebuffer(ioctl mmap)

bpp: 每个像素用多少位来表示它的颜色

首地址+偏移地址=确定出位于哪里

假设fb_base是APP执行mmap后得到的Framebuffer地址

求偏移地址: y*line_width+x*pixel_width(这就时偏移地址) + fb_base = 绝对地址

可以用以下公式算出(x,y)坐标处像素对应的Framebuffer地址:

(x,y)像素起始地址=fb_base+(xres*bpp/8)y + xbpp/8

一行的宽度: xres * bpp/8 一个像素宽度: bpp/8

ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数 。所谓对I/O通道进行管理,就是对设备的一些特性进行控制

ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。

头文件:#include <sys/ioctl.h>

函数原型: int ioctl(int fd, unsigned long request, ...);

函数说明:

① fd 表示文件描述符;

② request表示与驱动程序交互的命令,用不同的命令控制驱动程序输出我们需要的数据;

③ … 表示可变参数arg,根据request命令,设备驱动程序返回输出的数据。

④ 返回值:打开成功返回文件描述符,失败将返回-1。

ioctl的作用非常强大、灵活。不同的驱动程序内部会实现不同的ioctl,APP可以使用各种ioctl跟驱动程序交互:可以传数据给驱动程序,也可以从驱动程序中读出数据。

mmap

#include <sys/mman.h>
函数原型:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

函数说明:
① addr表示指定映射的內存起始地址,通常设为 NULL表示让系统自动选定地址,并在成功映射后返回该地址;
② length表示将文件中多大的内容映射到内存中;
③ prot 表示映射区域的保护方式,可以为以下4种方式的组合
a. PROT_EXEC 映射区域可被执行
b. PROT_READ 映射区域可被读出
c. PROT_WRITE 映射区域可被写入
d. PROT_NONE 映射区域不能存取
④ Flags 表示影响映射区域的不同特性,常用的有以下两种
a. MAP_SHARED 表示对映射区域写入的数据会复制回文件内,原来的文件会改变。
b. MAP_PRIVATE 表示对映射区域的操作会产生一个映射文件的复制,对此区域的任何修改都不会写回原来的文件内容中。
⑤ 返回值:若成功映射,将返回指向映射的区域的指针,失败将返回-1

映射framebuffer, framebuffer时驱动程序分配的, 应用程序想要去使用必须使用mmap映射到用户空间, 应用程序得到LCD的参数并且得到framebuffer的地址之后就可以在上面操作了.

描点函数demo:

28 void lcd_put_pixel(int x, int y, unsigned int color(传入RGB888))
29 <

以上是关于嵌入式基础的主要内容,如果未能解决你的问题,请参考以下文章

如何在嵌入式LINUX中增加自己的设备驱动程序

自定义通信协议设计基础

20155229 2017-2018-1 《信息安全系统设计基础》实验四 外设驱动程序设计

软考-嵌入式系统设计师-笔记:嵌入式系统的安全性知识

第六章随笔

sqlite基础语法