u-boot配置与编译、kernel的裁剪添加移植、busybox根文件系统的制作等

目录

1.裸机... 3

1.1裸机烧写系统... 3

1.2裸机启动流程分析... 3

1.2.1 BL0:... 3

1.2.2 BL1:... 4

1.2.3 BL2:... 4

1.3那么裸机怎么启动呢:编写裸机代码,比如说让灯亮... 4

2.uboot基本使用... 5

2.1uboot文件目录介绍... 5

2.2uboot的启动方式... 6

2.3uboot编译烧写... 6

2.4uboot的基本命令... 7

2.5Bootloader 启动的两个阶段... 8

3. linux内核... 8

3.1内核功能:... 8

3.2linux目录介绍... 8

3.3内核编译/烧写过程... 9

4.文件系统... 10

4.1介绍:... 10

4.2 搭建一个文件系统:... 11

5.NFS文件系统服务的搭建... 14

5.1 NFS概念:... 14

5.2工作原理:... 14

5.3 NFS服务的搭建... 14

5.4 NFS服务的测试... 16

6.内核模块化编程... 16

6.1 模块化介绍... 16

6.2 静态编译到内核步骤:比如说写个led的驱动... 16

6.3 动态编译到内核的步骤:... 17

6.4 单模块框架:... 17

6.5编写Makefile: 18

6.6多模块:... 18

6.6.1模块2调用模块1中函数... 18

6.6.2多个模块编译为1个模块... 18

6.7 向模块传参: 18

7.驱动简介... 20

8.那么,对于杂项设备来说:... 20

8.1注册函数:int misc_register(struct miscdevice *misc); 20

8.2注销函数:int misc_deregister(struct miscdevice *misc) 20

8.3特点:... 21

9.那么,对于早期经典设备来说:... 21

9.1注册函数:int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) 21

9.2注销函数:static inline void unregister_chrdev(unsigned int major, const char *name) 21

9.3特点:... 22

10.那么,对于linux2.6版本注册来说:... 22

10.1首先,申请设备号:... 22

10.2然后,添加设备:... 23

10.3最后,自动创建设备节点:... 23

10.4前面的一系列的操作完整的完成的设备驱动程序,然后就是注销... 23

11.理解... 24

附录... 25

整个系统的启动流程图:... 25

u-boot分区结构:... 25

SD卡的详细分区:... 26

exynos4412的内部存储图:... 26

 

 

 

 

 

 

 

1.裸机

1.1裸机烧写系统

  1. 我们是通过SD卡(启动方式)来烧写系统,所以我们首先要给分区
  2. 把操作系统和恶化配置文件给cp到SD卡,然后把配置文件给修改成你想烧写的系统
  3. 把SD插在板子上,选择启动方式为SD卡

1.2裸机启动流程分析

1

 

简单地说,iROM(BL0)就是先设置程序运行环境 (比如关看门狗、关中断、关MMU 、设置栈 、设置栈 、启动 PLL );然后根据OM引脚确定启动设备 (NAND Flash/SD /其他 ),把 BL1 从里面读出存入iRAM;最后启动 BL1.

1.2.1 BL0

三星公司固化到SOC内部的ROM(iROM),里面主要是尽可能只做uboot加载,尽可能消除其他的影响(关闭WDT、禁用IRQ、关闭cache等等),接下来就是从外设拷贝加载BL1。

1.2.2 BL1

没什么特殊功能,特点就是经过加密的,而且BL1由三星公司提供好的,完成对BL2的加载到iRAM运行。要想在开发板上移植运行后续的代码,通过签名的方式实现软件和硬件合法性的匹配

1.2.3 BL2

由平台研发人员编写,根据SOC外内存硬件不同、工作频率不同,进行初始化设置,完成后续代码加载到初始化后的内存中运行(逻辑代码只占前14k,如果写超了的话,会被裁剪)

1.3那么裸机怎么启动呢:编写裸机代码,比如说让灯亮

  1. 编译:首先呢,需要编写一个脚本,把led.c编译成led.o,然后把led.o链接到BL2的起始地址处(0x0202_3400)生成led.elf,然后在把.led.elf编译生成led.bin二进制文件
  2. V310-EVT1-mkbl2.c:/*复制出前14K内容,把前(14*1024-4)=14332字节做个和校验(checksum)得到一个4字节的校验码,再把它放到14332字节之后,输出一个新的14K的二进制文件*/
  3. 把V310-EVT1-mkbl2.c编译生成mkbl2,用mkbl2工具把led.bin生成bl2.bin,这里需要的文件有E4412_N.bl1.bin(这个是由三星公司提供的)
  4. 插上SD卡,通过脚本文件把bl2.bin烧写到/dev/sdb中,./fast_fuse.sh /dev/sdb   ../led.bin

2.uboot基本使用

bootlaoderbootloader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境

uboot:是bootlaoder的一种,通用版

2.1uboot文件目录介绍

fs:支持的文件系统

include:编译需要的头文件

mkconfig:配置的脚本文件

api: 函数接口

arch: 体系架构相关的文件,我们用的一般是:arch/arm/cpu:存放是cpu分类,armv7 /--Cortex-A8/9 系列CPU , 典型代表 Exynos4412

boards.cfg:给定一个名字 得到他对应的CPU架构、CPU、开发板名字(可以找到他对应的arch以及board下面的文件)

board:按照CPU厂家分类,具体内部又以开发板型号分类,我们用到的是board/samsung下的

common:通用的多功能函数实现,如环境,命令,控制台相关的函数实现。

disk:磁盘相关文件

Drivers:各种外设驱动

example:例程代码

fs:支持的文件系统:支持文件系统的文件,u-boot现在支持cramfs、fat、fdos、jffs2、yaffs和registerfs。

Lib:u-boot通用库函数,ARM的公共函数,比如printf

Post:电源管理。电源检测相关函数。如果没有它,例如电脑cpu风扇去掉了,没有该函数,那么容易烧cpu,如果有post函数,可以检测到是否有风扇,如果没有则不启动cpu即保护了cpu。

2.2uboot的启动方式

自启动模式( Boot laoding):这种模式下, bootloader 从目标机上的某个固态存储设备(这里是SD卡上的BL2区域)上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。产品发布后, bootloader 工作在这种模式下。

交互模式:在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主机下载文件(比如内核映像、文件系统映像)到 RAM 中,可以被 bootloader 写到目标机上的固态存储介质中,或者直接进入系统的引导。

可以利用串口对uboot传递参数,在倒计时结束前按enter即可进行设置。

2.3uboot编译烧写

(1)把今天资料中uboot_tiny4412-sdk1506.tar.bz2文件夹拷到 /root/workdir

       (2)解压 tar zxvf uboot_tiny4412-sdk1506.tar.gz

        (3)cd uboot_tiny4412-sdk1506

        (4)make tiny4412_config

        (5)make  

        (6)cd sd_fuse

        (7)make

        (8)cd tiny4412

(9)插SD卡

        (10)./sd_fusing.sh  /dev/sdb

测试u-boot

连接好 串口线,连接到电脑,上电,选择SD为启动方式,映射开发板到windows操作系统(通过串口)

 

2.4uboot的基本命令

可以通过这些指令对u-boot启动前的配置和查看

uboot环境变量的基本配置流程

查看:printenv、print、pri 打印全部环境变量

设置:set、setenv 新增、修改、删除环境变量

保存:save 保存环境变量

重启:reset

一些常用的命令:

?或者help :打印支持所有的命令

version:查看版本

? xxx命令 :打印xxx命令的使用规则(相当于man)

pri/:打印环境变量

set :设置环境变量

ping :测试网络连通性

reset :复位CPU

loady:(使用串口下载代码到内存中)

md: 查看内存数据  比如:md.b 0x02023400 查看0x02023400有多少字节

 cp :内存拷贝(复制数据从内存到另一个内存cp 0x02043400  0x40000000  0x10)

bdinfo: 查看配置信息

 

2.5Bootloader 启动的两个阶段

1.Bootloader 第一阶段的功能:
1) 硬件设备初始化。
2) 为加载 Bootloader 的第二阶段代码准备 RAM 空间。
3) 设置 CPU 的速度、时钟频率及终端控制寄存器
4) 初始化内存控制器
5) 复制 Bootloader 的第二阶段代码到 RAM 空间中。
6) 设置好栈。
7) 跳转到第二阶段代码的 C 入口点

2.Bootloader 第二阶段的功能:
1) 初始化本阶段要使用的硬件设备。
2) 检测系统内存映射( memory map)。
3) 将内核映像和根文件系统映像从 FLASH上读到 RAM空间中。
4) 为内核设置启动参数。
5) 调用内核。

3. linux内核

3.1内核功能:

linux下一切皆文件,当然linux内核也是,是由3万多个文件组成的内核,这3万多个文件共同完成五大功能:

  1. 进程管理(cpu) 
  2. 内存管理(内存)
  3. 设备管理(驱动)
  4. 网络管理(网络协议tcp/ip)
  5. 文件系统管理(vfs)

3.2linux目录介绍

arch:(分为了三类文件 ,第一类为公用的文件,第二类是具体芯片相关的mach开头的,第三类是系列公共文件plat开头的)目录是架构相关的

block:块设备文件(u盘,硬盘)

crypto:目录是一些加密算法相关的c代码

Documentation:目录说明文档,是关于linux系统的各个部分的一个详细的说明文档

drivers:目录是重点设备驱动

fs:文件系统实现

init:目录初始化(linux系统c程序的一个入口系统运行起来第一个运行起来的第一个进程就是里边的main.c)

ipc:进程间的通信Linux支持多进程通信进程间通信相关的机制都在此目录

kernel:真正的和硬件无关的放在这里(其实kernel分为两部分,一部分是和硬件相关的内容(arch/arm/kernel),一部分是和硬件无关的内容)

lib:跟硬件无关的库函数都放在此目录

mm:内存管理相关的支持文件

net:各种网络通信协议相关的文件

samples:示例程序

scripts:存放的都是脚本文件(内核配置会用的)

security:系统支撑安全的一些相关代码

sound:声卡驱动

tools:编译内核所需要的一些工具

usr:用户程序

Kconfig:

    make menuconfig是检索所有目录中的Kconfig生成菜单的。

    kconfig:内核地图

Kconfig 入口点:顶层目录Kconfig

COPYLNG:是记录版权信息的

    CREDITS:光荣板信息(也就是哪些对内核做过贡献)

    Kbuild:编译内核的一些相关脚本

    Kconfig:内核的菜单建立内核配置菜单的一个相关文件

    MAINTAINERS:记录了内核的每一个部分有哪些人员在维护

    Makefile:记录内核文件的依赖关系以及如何编译的哪些需要编译如何连接

    README:一些说明信息

3.3内核编译/烧写过程

1.在顶层目录修改makefile,指定交叉编译工具

 

2.makefile只会编译目录下.config文件内指定的内容,所以我们对内核裁剪只需要修改.config文件就可以了,但是直接修改.config文件非常麻烦,于是就提供了一个图形化菜单给用户使用,在菜单内进行配置,配置完成自动生成.config文件

3.具体配置步骤

make menuconfig  //使用图形化界面修改.config,使用时要把字体调小

如果make menuconfig出现错误就执行如下命令

1.apt-cache search ncurses

2.apt-get install libncurses5-dev

然后进去图形化界面去掉一个内核保护选项(内核的一个保护机制):

System Type -->

         [ ] Support TrustZone-enabled Trusted Execution Environment

4.可以对内核进行裁剪和添加:

裁剪:就是在图形化界面上不选择那个选项,当时就不会编译

添加:

第一步:写一个驱动代码放在drivers/char/中;

第二步:在kconfig文件中加选项;

第三步:在Makefile中把自己写的加里面;

第四步:进顶层,进入图形化界面,选择刚才添加的驱动;

第五步:这个时候就可以在.config中找到自己添加的驱动程序:文字中的 y 表示选择进内核。 m表示编成模块。 n 表示不选择

5. make  编译内核   编译之后生成的arch/arm/boot 下的zImage

6. make zImage  // 生成内核镜像

7. 烧写

(1)将烧写脚本文件fush_uimage拷贝到  arch/arm/boot(或者直接在arch/arm/boot  目录终端输入 dd iflag=dsync oflag=dsync if=./zImage of=/dev/sdb seek=1057)

(2)执行脚本把镜像文件烧写到SD卡

 

 

4.文件系统

4.1介绍:

  1. 包括根文件系统和建立于 FLASH 内存设备之上的文件系统。里
    面包含了 LINUX 系统能够运行所必须的应用程序、库等,比如可以
    给用户提供操作 LINUX 的控制界面和 shell 程序、动态连接和程序运
    行时需要的 glibc uClibc 库等。
  2. linux内核运行之后需要挂接一个文件系统,相当于电脑的硬盘,用来存储程序,为了开发方便,所以我们挂接了一个网络文件系统

3. 根文件系统目录结构介绍

/bin

必备的用户命令,例如 lscp

/sbin

必备的系统管理员命令,例如 ifconfigreboot

/dev

设备文件,例如 mtdblock0tty1

/etc

系统配置文件,包括启动文件,例如 inittab

/lib

必要的链接库,例如 C 链接库、内核模块

/home

普通用户主目录

/root

root 用户主目录

/usr/bin

非必备的用户程序,例如 finddu

/usr/sbin

非必备的管理员程序,例如 chrootinetd

/usr/lib

库文件

/var

守护程序和工具程序所存放的可变,例如日志文件

/proc

用来提供内核与进程信息的虚拟文件系统,由内核自动生成目录下的
内容

/sys

用来提供内核与设备信息的虚拟文件系统,由内核自动生成目录下的
内容

/mnt

文件系统挂接点,用于临时安装文件系统

/tmp

临时性的文件,重启后将自动清除


 

 

 

4.2 搭建一个文件系统:

 

我们使用的是一个名为busybox的工具制作根文件。BusyBox 是一个集成了一百多个最常用 linux 命令和工具的软件。BusyBox 包含了一些简单的工具,例如 lscat echo 等等,还包含了一些更大、更复杂的工具,例 grepfindmount 以及 telnet

步骤:

  1. 拷贝 busybox 源码包到虚拟机个人文件夹
  2. 解压源码包
  3. 进入解压的文件夹 busybox-1.21.1 进行安装选项配置  执行make menuconfig
  4. 设置共享库:

Busybox Settings --->
Build Options --->
   [*] Build shared libbusybox

  1. 指定交叉编译链(注意 arm-linux-后面不要有空格)

Busybox Settings --->
Build Options --->
    () Cross Compiler prefix (NEW)

  1. 设置安装路径(路径可以自己指定,就是接下来会安装生成的根文件系统,习惯上目录层次不要太深,方便之后使用)
  2. Busybox Settings --->
      Installation Options ("make install" behavior) --->
          (./install) BusyBox installation prefix(NEW)
  3. 添加模块指令make menuconfigà Linux Module Utilities
  4. 编译安装

#make && make install

安装完成之后就会在刚才配置菜单当中指定的路径下找到生成的根文件系统目录 rootfs

10. 复制命令的动态库: 由于配置 busybox 时候采用动态链接方式编译,所以,要把它所依赖的动态库文件复制到安装目录 rootfs

11. 删除不必要的静态库,在根文件系统 rootfs/lib 目录执行:
# rm lib/*.a –f

12. 创建其他目录, 生成完整根文件系统目录结构

mkdir dev etc/init.d home proc sys root opt tmp var –p

13. 创建系统启动必要的设备节点

    (1) /dev/console 控制台

    (2) /dev/null 万能的垃圾桶

    在“ 根文件系统”(这里对应 rootfs) 创建设备节点

# mknod dev/console c 5 1
# mknod dev/null c 1 3

14. 构建 etc 目录下的系统配置文件

1.创建fstab文件

作用:这个文件描述系统中各种文件系统的信息。一般而言,应用程序仅读取这个文件,而不
对它进行写操作。对它的维护是系统管理员的工作。在这个文件中,每个文件系统用一行来描述,在每一行中,用空格或 TAB 符号来分隔各个字段,文件中以#开头的行是注释信息。Fstab 文件中的纪录的排序十分重要。因为 fsckmount umount 等程序在做它们的工作时会按此顺序进行

(1)cp /etc/fstab ./etc

(2)修改 fstab 文件内容为以下(注意类型顺序和每两个单词之间都是tab键的)

# device mount-point              type       options   dump   fsck      order
proc            /proc     proc      defaults        0    0
tmpfs     /tmp     tmpfs   defaults       0   0
sysfs      /sys      sysfs     defaults  0   0
tmpfs    /dev       tmpfs    defaults  0   0

  1. 创建 inittab 文件

# cp /xyd/busybox-1.21.1/examples/inittab ./etc/
# vim etc/inittab

修改 inittab 文件内容为以下:
::sysinit:/etc/init.d/rcS 启动系统初始化文件/etc/init.d/rcS
console::askfirst:-/bin/sh 在串口终端启动 askfirst 动作的 shell
::ctrlaltdel:/sbin/reboot 当按下 Ctrl+Alt+Delete 组合键时, init 重启执行程

::shutdown:/bin/umount -a –r 关机时运行 umount 命令卸载所有的文件系统,如
果卸载失败,试图以只读方式重新挂载

  1. 创建 etc/init.d/rcS 文件, 添加内容如下:

Vim etc/init.d/rcS

内容:

#!/bin/sh 声明 shell 脚本类型, 使用 busybox shell
mount –a
将文件 /etc/fstab 中指明的文件挂载到对应的
挂载点
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
热插拔事件时产生设备节点的支持
mdev -s
/bin/hostname XYD

然后给rcS 文件添加执行权限:chmod +x etc/init.d/rcS

  1. 创建 etc/profile 文件

Vim etc/profile

添加内容:

USER="`id -un`"
LOGNAME=$USER
PS1='[\u@\h \W]# '
PATH=$PATH
HOSTNAME=`/bin/hostname`
export USER LOGNAME PS1 PATH

  1. 实现提示符功能还需要两个文件的支持,拷贝虚拟机 linux 目录的 etc/ group
    etc/passwd 的文件到根文件系统
    # cp /etc/group /etc/passwd ./etc/

5.NFS文件系统服务的搭建

5.1 NFS概念:

NFS(NetWork Filesystem System)可以用于不同操作系统之间通过网络传输文件,在嵌入式开发领域,NFS可以用于主机与嵌入式设备之前无缝传输文件,由于嵌入式设备的存储空间普遍较为有限,因此也可以用此工具扩展嵌入式设备的存储空间。

5.2工作原理:

NFS服务是基于客户机/服务器模式,NFS服务器是提供输出文件(共享文件)的计算机。NFS客户端是访问输出文件的计算机,它可以将输出目录挂载到自己系统中的某个目录中,然后像访问本地文件一样去访问NFS服务器中的输出文件。

 

5.3 NFS服务的搭建

  1. 安装nfs服务:sudo apt-get install nfs-kernel-server  nfs-common
  2. 修改配置文件:

sudo vim /etc/exports

注:“exports”文件用于配置NFS服务器中输出的共享目录

修改内容如下:

/root/rootfs  *(rw,sync,no_root_squash,no_subtree_check)//中间空格必须是Tab

注:/root/rootfs为nfs服务客户端共享的目录,可自行创建,这个路径必须为绝对路径。

*:允许所有的网段访问,也可以使用具体的IP

192.168.1.* 指定网段,在该网段中的用户可以挂载

ro : 只读

rw:挂接此目录的客户端对该共享目录具有读写权限

sync:资料同步写入内存和硬盘

no_root_squash:root用户具有对根目录的完全管理访问权限。

no_subtree_check:不检查父目录的权限

  1. 重启nfs服务和rpcbind 服务

                 sudo /etc/init.d/nfs-kernel-server  restart

 sudo /etc/init.d/rpcbind  restart

  1. 设置 NFS 开机自动启动:chkconfig nfs on
  2. 设置虚拟机网络 IP(注意虚拟机、物理机、开发板 linux 内核的 IP 为同一号段)

# service network restart
# ifconfig

  1. 虚拟机网络设置,配置为“桥接”: VMware 右下角网络适配器选择“ 设置” 更改为“ 桥接模式”
  2. 虚拟机 VMware 选项卡“编辑” --虚拟网络编辑器” --桥接设置桥接到物理网络
  3. 重启网络
    # service network restart
  4. 关闭防火墙
    # service iptables stop
  5. 重启 NFS 服务
    # service nfs restart
  6. 挂接不成功时记得查看根文件系统 etc 目录下的 rcSfstabinittab 等文件是否有执
    行权限,如果没有,加执行权限
  7. 配置物理机网络连接

windows 打开网络和共享中心更改适配器设置--以太网/本地连接--右击属性--IPv4
IP 设置为虚拟机同一号段,这里是 192.168.15.16

  1. 关闭 windows 防火墙

网络共享中心 — Windows 防火墙 启用或关闭 windows 防火墙 --
关闭 确定(在控制面板也可以找到)

  1. 进入 uboot 修改 bootargs:这时打开超级终端后,启动流程就是uboot--kernel--根文件系统,但是此时我们需要对uboot进行修改,能够挂载上文件系统

#set bootargs 'noinitrd root=/dev/nfs nfsroot=192.168.15.2:/xyd/rootfs ip=192.168.15.5:192.16
8.15.2:192.168.15.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 lcd=S70'
# save
# reset
  

5.4 NFS服务的测试

  1. 检查客户端和服务端的网络是否连通(ping命令)

ping  服务主机IP

  1. 查看服务端的共享目录

showmount  -e  服务主机IP

  1. 将与nfs服务客户端共享的目录挂载到本地

mount -t nfs -o nolock 服务主机IP: /root/rootfs  /mnt 

注:/mnt 指定将共享目录挂载的路径, -t nfs 指定挂在协议是那台ip地址的主机,mount nfs时,默认选项包括文件锁,依赖于portmap提供的动态端口分配功能,因此需要解锁,因此一般直接在指令中直接加上-o nolock。

  1. 通过访问/mnt即可访问共享目录的内容
  2. 取消挂载

umount  /mnt

 

6.内核模块化编程

6.1 模块化介绍   

在c语言中:按照功能将一个.c文件拆分为多个.c以及.h文件

在单片机的时候:对应每一个外设都有一个.c和.h文件,.c文件写模块的初始化以及功能函数,.h文件对应函数的声明,如果想使用该外设只需包含其头文件即可。

但是在驱动里不同:内核的功能很强大,如果我们想将自己的某一个模块加载到内核,由两种方式:

  1. 修改内核源码重新编译烧写内核(模块功能稳定)—静态编译
  2. 以模块的方式动态加载(调试阶段),重启后无效—动态编译

6.2 静态编译到内核步骤:比如说写个led的驱动

  1. 首先在/driver/char下写驱动程序
  2. 在kconfig中添加驱动选项
  3. 在Makefile中添加自己写的驱动程序,等待图形化选择编译
  4. 回到顶层,进入图形化界面,选择刚才添加的驱动

6.3 动态编译到内核的步骤:

  1. 我们需要把.c文件给编译成.ko文件,然后需要把模块动态的加载到内核中(insmod xx.ko)
  2. 当然动态编译 具有还原性,可以卸载(rmmod xx.ko)

6.4 单模块框架:

驱动文件:

  1. 头文件:

#include <linux/module.h>//模块相关函数的头文件声明

#include<linux/kernel.h>//printk函数的声明头文件

  1. 加载函数模板: static int __init (函数名) (void)
  2. 卸载函数模板:static void __exit (函数名)(void)
  3. 声明:

module_init -指定加载函数

module_exit -指定卸载函数

MODULE_LICENSE("GPL");//声明开源协议

其他:

MODULE_AUTHOR // 声明作者

MODULE_DESCRIPTION // 对模块简单的描述

MODULE_VERSION // 声明模块的版本

MODULE_ALIAS // 模块的别名

MODULE_DEVICE_TABLE // 告诉用户空间这个模块所支持的设备

6.5编写Makefile:

(1)指定自己的内核路径KERN_DIR = /root/linux-3.5

(2)编译文件名obj-m += first_module.o

最后:编译后将.ko文件拷贝到文件系统,执行insmod xxx.ko 、lsmod、rmmod xx.ko

6.6多模块:

分为两种(第一种模块2调用模块1中函数)(第二种多个模块编译为1个模块)

6.6.1模块2调用模块1中函数

  1. 模块2 需要声明外部函数
  2. 模块1要声明该函数可以被外部调用 EXPORT_SYMBOL( )
  3. 先加载模块1 再加载模块2
  4. 先卸载模块2 再卸载模块1

6.6.2多个模块编译为1个模块

  1. 如果一个模块只是单纯调用外部函数只需声明即可

6.7 向模块传参:

介绍:加载模块的时候动态传递参数//  insmod xxx.ko 1 2 3

(1)module_param(par,int,S_IRUGO|S_IWUSR);

(2)module_param(str,charp,S_IRUGO);

(3)module_param_array(arr,int,&num,S_IRUGO);

(1)和(2)传递变量和字符串;(3)是传递数组,这里的第二个参数就是数组的类型,第三个参数是存放传入参数元素数量,使用时候这里要写一个变量的地址

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7.驱动简介

目的驱动:让硬件可以工作,驱动不会主动执行都是被调用然后做初始化工作让硬件可以工作

怎么办:为了让硬件在linux下运行,我们采用的办法就是将硬件注册为一个文件节点,供应用层读写操作      

分类:字符设备;块设备;网络设备。

这里详解字符设备:

1.杂项设备注册

2.早期经典设备注册

3.Linux2.6设备注册

注:对于每一个设备来说,都经历3个过程:

1.设备号(主,次)的审请

2.注册添加设备(设备号与设备绑定)

3.创建设备节点

 

8.那么,对于杂项设备来说:

8.1注册函数:int misc_register(struct miscdevice *misc);

核心结构体:

struct miscdevice  {

         int minor;//次设备号(0-254),取255系统自动分配

         const char *name;//设备节点名,自动生成在/dev/下

         const struct file_operations *fops;//文件操作集

         struct list_head list;

         struct device *parent;

         struct device *this_device;

         const char *nodename;

         umode_t mode;

};

8.2注销函数:int misc_deregister(struct miscdevice *misc)

int misc_deregister(struct miscdevice *misc)

{

         int i = DYNAMIC_MINORS - misc->minor - 1;

 

         if (WARN_ON(list_empty(&misc->list)))

                   return -EINVAL;

 

         mutex_lock(&misc_mtx);

         list_del(&misc->list);

         device_destroy(misc_class, MKDEV(10, misc->minor));

         if (i < DYNAMIC_MINORS && i >= 0)

                   clear_bit(i, misc_minors);

         mutex_unlock(&misc_mtx);

         return 0;

可以看出device_destroy就是摧毁设备节点,那个节点取决于传的次设备号,因为主设备号已经确定,设备号和设备节点是对应的。

8.3特点:

1.主设备号指定为10,次设备号取值范围0-254;

2.一个注册函数完成所以步骤,即设备号的申请、设备号和设备的绑定、自动创建设备节点在/dev/下;

3.调用注册函数每次只能申请一个设备号,即我觉得在驱动不同种类的设备时用杂项设备注册发适用

9.那么,对于早期经典设备来说:

9.1注册函数:int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)

major:主设备名0-255

name:设备名,而非设备节点名。在/proc/device下

fops:文件操作集

9.2注销函数:static inline void unregister_chrdev(unsigned int major, const char *name)

major:主设备号,需要打印出来,手动创建设备节点时候需要

name:设备名,而非设备节点名,和注册函数的参数一样

9.3特点:

1.主设备号取值范围为0-255,取0系统自动分配,一旦调用注册函数,会自动分配一个当前没有贝使用的主设备号,以及该主设备号下的所以次设备号

都被申请占用

2.一个注册函数完成了申请设备号、设备号与设备绑定,但是没有自动的创建设备节点,需要自己mknod /dev/xxx c 主设备号 次设备号

3. 没有使用一个结构体进行封装,没有做一个整体描述

4. 注销释放的是设备名而非设备节点,因为设备节点是自己手动创建的

 

10.那么,对于linux2.6版本注册来说:

两个核心结构体先摆在这里:

struct cdev {

     struct kobject kobj;

         struct module *owner;//指定为THIS_MODULE,表示这个模块

         const struct file_operations *ops;//设备节点管理的文件指针集

         struct list_head list;//链表上下一个设备

         dev_t dev;//完整设备号

         unsigned int count;//申请设备号的数量

};

struct device *device_create(

struct class *cls, // 设备的类 类指针 让那个类来管理

struct device *parent, // NULL

dev_t devt, // 主设备号和次设备号的组合 32 位,主 12 位 次 20 位

void *drvdata, // NULL,设备私有数据

const char *fmt, … /*可以格式化的 fmt:/dev/下的节点名*/

);

申请设备号、注册添加设备(申请的设备号,和设备号绑定对应)、创建节点是分开做的,而非一个注册函数解决的。

 

10.1首先,申请设备号:

那么就需要将主设备号和次设备号整合成32位的,也需要从32位的完整设备号取出主设备号和次设备号,有相关的函数

MKDEV(ma,mi)//已知次设备号合成完整设备号

MpAJOR(dev)//从完整设备号提取主设备号

MINOR(dev)//从完整设备号提取次设备号

动态设备号申请:int alloc_chrdev_region(dev_t  *dev,  unsigned  baseminor,  unsigned count,  const  char *name)

dev:存放分配到的第一个设备(包括主次设备号)的指针,函数成功后会自动分配

baseminor:要分配起始次设备号(次设备号的起始值)

count:连续的次设备号数量

name:设备名,不需要和/dev的设备文件名相同

静态设备号的分配:int register_chrdev_region(dev_t from, unsigned count, const char *name)

from:自己指定的完整设备号

count:连续的次设备号数量

name:设备名,不需要和/dev的设备文件名相同

 

10.2然后,添加设备:

添加设备之前需要先核心结构初始化void cdev_init(struct cdev *cdev,const struct file_operations *fops)  

设备注册函数:int cdev_add(struct cdev *p,dev_t dev,unsigned count) 

参数p:已经初始化的核心结构指针

参数dev:起始设备号(包含主次设备号)

参数count:连续次设备号的数量

 

10.3最后,自动创建设备节点:

首先呢,需要创建class类:class_create(ower,name)

ower:类的所有者,我们一般会写THIS_MODULE代表这个模块

name:类的名字,自己随机取的

然后,创建设备节点:(struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt,.....))

class:上面创建类的返回值,是个指针

parent:父设备,一般没有就写NULL

devt:完整的设备名

drvdata:驱动数据,也可以是NULL,一般为NULL

fmt,...可变参数,用来生成/dev/目录下的设备文件名

函数成功返回一个指向节点描述结构体的指针

 

10.4前面的一系列的操作完整的完成的设备驱动程序,然后就是注销

 

1.摧毁设备节点 void device_destroy(struct class *class,dev_t  devt)

2.摧毁类 void class_destroy(struct class *cls)

3注销cdev结构空间 void cdev_del(struct cdev *p)  

4释放设备号 void unregister_chrdev_region(dev_t from,  unsigned  int count)

这个注销的顺序不能随意

 

11.理解

那么我们该如果选择三种注册方式呢?

其实,看上去都可以,但是如果选择不当会很复杂,比如说,我想同种类的设备,像多个usb,用早期经典注册方式和linux2.6注册方式就比较利落些,因为它可以在同一个主设备下开多个次设备,这样我们就可以创建多个设备节点使用同一个设备

疑问,设备节点,驱动,硬件设备是如何关联到一起的呢?

百度后得知:这是通过设备号实现的,包括主设备号和次设备号。当我们创建一个设备节点时需要指定主设备号和次设备号。

应用程序通过名称访问设备,而设备号指定了对应的驱动程序和对应的设备。主设备号标识设备对应的驱动程序,次设备号

由内核使用,用于确定设备节点所指设备。

什么叫做设备节点?

连接内核与用户层的枢纽,是设备是接到对应哪种接口的哪个ID 上。

 

附录

整个系统的启动流程图:

u-boot分区结构:

SD卡的详细分区:

 

exynos4412的内部存储图: