Linux(野火-鲁班猫-笔记)
本文章作为记录。
本文章适用于刚接触rk鲁班猫的小白,方便快速入门,部分更详细内容未体现,见谅
因图片上传失败,附 文末有baidu 网盘 pdf文档链接
Linux 一些常用命令
-rwxrwxrwx
“-” 最前面代表是文件 若为d 则为目录
后面三组rwx分别为拥有者,同组和其他人的可读可写可执行权限
cp 复制
cp 源路径文件 目标路径 //复制文件到指定路径
cp -r 源路径文件夹 目标路径 //用于复制文件夹到指定路径
mv 移动
mv 源路径文件/文件夹 目标路径文件/文件夹 //移动文件/文件夹 到目标路径 注:“.”为当前路径
mv 源路径文件/文件夹 同一路径文件/文件夹 //修改文件夹/文件名字
mkdir 文件夹的创建
mkdir /当前文件夹/创建的文件夹名字
寄存器操作
例题:
利用16位寄存器的位操作实现下列功能
1)将寄存器BSRR的第6位置1 BSRR |= (1<<6);
2)将寄存器BSRR第六位清零 BSRR &=~(1<<6);
- 将寄存器BSRR第3 、6 位置1 BSRR|= (1 << 3) | (1 << 6);
4 )将寄存器BSRR第3 、6位清零 BSRR&=~( (1<<3) | (1<<6) );
STM32单片机 MCU
STM32F系列单片机 GPIO有哪些模式?
浮空输入、上拉输入、下拉输入、模拟输入、开漏输出、推挽输出、复用开漏输出、复用推挽输出、
gcc 编译过程
gcc -E hello.c -o hello.i //预编译
gcc -S hello.i -o hello.s //编译
gcc -c hello.s -o hello.o //汇编
gcc hello.o -o hello.c //链接
预编译:
预编译过程主要处理了 “#”那些源代码中的以"#“开始的预编译指令。比如”#include",“#define"等。主要的处理规则如下:
(1)将所有的”#define"删除,并且展开所有的宏定义。
(2)处理所有条件预编译指令,比如"#if",“#ifdef”,“#elif”,“#else”,“endif”
(3)处理"#include"预编译指令,将被包含的文件插入到该预编译指令的位置。
(4)删除所有注释"//“和”/* */"
(5)添加行号和文件名标识。比如#2"hello.c"2。
(6)保留#pragma指令,因为编译器必须使用它们。
编译:
存储在静态存储区的数据有哪些?
可编程内存在基本上分为这样的几大部分:静态存储区、堆区和栈区。他们的功能不同,对他们使用方式也就不同。
静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。
栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。
代码区:存放函数体的二进制代码
文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
函数指针指向Code区,是程序运行的指令代码,数据指针指向Data,Heap,Stack区,是程序依赖以运行的各种数据
在文件作用域声明inline函数默认为static存储类型,const常量默认为static存储,如果加上extern,则为外部存储类型。
会话 (session)
会话是指一个终端用户与交互系统进行通讯的过程。
守护进程 创建步骤
1:创建子进程,并退出父进程
2:创建新会话
3:修改工作目录 //为根目录
4:设置权限掩码
5:关闭其他文件描述符
例子:
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
pid_t p=fork();
if(p<0)
{
perror("fork failed\n");
exit(-1);
}
else if(p==0) //子进程
{
setsid(); //为子进程创建新会话
umask(0); ///**注释1*:---------------------------**
for(int i=0;i<3;i++) //文件描述符是非负整数,一般当系统打开时会占用0,1,2这三个文件描述符,将其关闭即可
close(i);
int fd=open("data.txt",O_RDWR);
while(1)
{
sleep(2); //每隔两秒就像文件中写入
write(fd,"hello world\n",11);
}
}
else //父进程
{
exit(0);
}
return 0;
}
注释1:*
静态库与动态库
库是一种二进制文件 (且没有main函数的)
**静态库:**是在程序执行前(编译)就加入到目标程序中去了
优点:运行快;发布程序无需提供静态库,因为已经在app中,方便移植;
缺点:大
**动态库:**是在程序执行时动态(临时)由目标程序去调用
优点:小
缺点:运行慢
静态库使用:
静态库的创建:
需要:带有主角函数的.c文件以及需要的各种.c非main文件
例:
//start.c
#include <stdio.h>
#include "fun.c"
void main()
{
funprintf("hello world!");
}
//fun.c
#include<stdio.h>
void funprintf(char *str)
{
printf("%s",str);
}
1.给库生成 .o文件
得到:
2.生成xxx.a静态库文件:
得到:
3.编译即可
得到
注意
- l是定制要用的动态库,库名砍头去尾;
- L告诉gcc编译器从-L指定的路径去寻静态库。默认是从/usr/lib或者/usr/local/lib去
另外,fun才是静态库名字,这点与window不太一样。
关于正点原子Linux alpha 开发板烧录问题
Linux 野火-鲁班猫——————————————↓
接下来将系统学习野火鲁班猫
鲁班猫是野火推出的高性能的卡片电脑。
编译(备注操作)
#清除之前生成的所有文件和配置
make mrproper
# 加载lubancat2_defconfig配置文件,rk356x系列均是该配置文件
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
# 编译内核,指定平台,指定交叉编译工具,使用8线程进行编译,线程可根据电脑性能自行确定
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j8
对于硬件信息
1.高主频(4核心 A55
2.大内存(1/2/4/8GB LPDDR4内存
3.大硬盘(8/32/64/128GB
4.强网络(2.5G OR 千兆
5.支持多显示(HDMI OR MIPI
6.高算力(1tops
主芯片RK3568,本教程针对
关于鲁班猫2与其他板载的对比
环境搭建
烧录
烧录分为SD卡烧录和EMMC烧录方式,我们的鲁班猫2可以有内置的EMMC,可以进行sd卡烧录和emmc烧录。那么。
SD卡-烧录软件
SDDiskToool RK官方烧录工具
因为我们一 般使用Ubuntu镜像,所以一般都是用该工具。
另外,我们需要sd卡的格式化工具
一般的,我们使用的Ubuntu版本为20.04
然后的,烧录文件命名中存在lite 表示无桌面版,有些其他教程也叫server版
然后是xfce版,即安装了xfce桌面的版本;xfce-full版,即在xfce基础上,安装了一些教育软件和一些办公软件等。
详细流程:
EMMC-烧录
容量8G、32G、64G、128G可选
RKDevtool2.9适用于Ubuntu
详细流程:
另外,关于烧录模式loader 和 maskrom
LINUX 的交叉编译器搭建
我们都知道Ubuntu有一个专门用来安装软件的工具apt,我们可以用它来全自动安装arm-linux-gcc。
此方法安装的是最新版的,如果你要安装的是某一特定版本的不推荐此方法!还要注意这个需要使用国内的apt软件源服务器,如果此方法不行的话就用方法二:
首先Ctrl+Alt+T弹出终端,使用如下命令进行arm-linux-gcc的安装:
sudo apt-get install gcc-arm-linux-gnueabihf
使用如下命令进行arm-linux-g++的安装:
sudo apt-get install g+±arm-linux-gnueabihf
如果要卸载时使用如下命令进行移除,arm-linux-gcc的卸载:
sudo apt-get remove gcc-arm-linux-gnueabihf
arm-linux-g++的卸载:
sudo apt-get remove g+±arm-linux-gnueabihf
神秘代码
sudo apt-get install curl python2.7 python-pyelftools git ssh make gcc libssl-dev liblz4-tool expect g++ patchelf chrpath gawk texinfo chrpath diffstat binfmt-support qemu-user-static live-build bison flex fakeroot cmake gcc-multilib g++-multilib unzip device-tree-compiler python3-pip libncurses-dev python3-pyelftools vim mtd-utils
Linux 内核文件夹
先放图
arch文件夹 架构目录,内部有,摩托罗拉、arm、x86架构,通常根据所使用的版型确定使用架构,然后进行裁剪
block文件夹 块设备文件夹,驱动相关
crypto 文件夹 加密相关文件夹,如输入的密码存储加密等
Documentation 文件夹 帮助文件夹,可以查看如何使用内核文件夹等
drivers 文件夹 驱动文件夹,包括spi驱动、块设备驱动、iic驱动、网卡驱动等
firmware 文件夹 存放标准协议文件
fs 文件夹 文件系统,支持超多文件系统
include 文件夹 绝大部分头文件在此
init 文件夹 初始化相关,里面有个main.c,值得从此处看
ipc 文件夹 进程间通讯所需支持文件夹
kernel 文件夹 包括调度、电源管理、中断、定时器、软中断、看门狗等
lib 文件夹 库文件夹
mm 文件夹 内存管理文件夹,内存管理、内存申请、内存映射等
net 文件夹 网络协议相关,对应的驱动在上面的driver文件夹中
sample 文件夹 底层例程等
scripts 文件夹 该文件夹是帮助我们配置、裁剪整个linux所使用的,一些方法等
security 文件夹 安全相关文件夹,一般不动
sound 文件夹 声卡文件夹,包括声卡驱动等,把声卡驱动单独在此处,drivers中无声卡驱动文件
tools 文件夹 内核使用的工具文件夹
usr 文件夹 初始的usr文件夹
virt 文件夹 映射 地址映射、IO映射等
功能实现
1、基础功能
1.字符设备驱动
2.块设备驱动
3.网络设备驱动 无
2、框架功能
框架:host驱动和设备驱动
驱动表现就是 /dev/xxx文件 打开、关闭、读写
现在新的内核支持设备树 这个一个 .dts 文件 此文件 描述了板子(产品)的设备信息
注意:Linux操作系统内核和驱动程序运行在内核空间,应用程序在用户空间
那么,应用程序需要访问内核的驱动以达到实现功能的目的,必然需要放分内核空间,但是应用程序在用户空间,所以 可以 :系统调用、异常和陷入 访问内核,一般使用 系统调用 但是也不是直接调用系统调用,而是通过API函数进行调用系统调用。(如 POSIX、API、C库)
每个系统调用都有系统调用号,应用程序软中断调用时,也会指定系统调用号。
流程如下:例:点亮LED
首先是应用程序打开文件(/dev/led),写入1或0达到开关效果,然后关闭文件(close)。
如何和驱动交互:
所以我们驱动也需要对应的open函数、close和write函数
关于驱动分类
字符设备驱动(最多) gpio、iic、spi、usb。。。
块设备驱动 EMMC 块存储等
网络设备驱动
且一个设备不是说一定只属于某一类型。如 USB WIFI ,SDIO WIFI,属于网络驱动,存在usb和sdio,因此也属于字符设备驱动。
修改登陆后启动输出信息
在根文件系统中,找到/etc/update-motd.d文件夹,如果是其他sdk包,可能有不同(如:设置启动服务、以及相关文件夹(/etc、/var、/run文件夹等)),登录打印信息的方式右很多种。
设备树
设备树的作用就是描述一个硬件平台的硬件资源。这个“设备树”可以被bootloader(uboot)传递到内核, 内核可以从设备树中获取硬件信息。
单独的一个设备树文件是以.dtsi为后缀,而且,其他设备数也可以引用。
- DTS 是指.dts格式的文件,是一种ASII 文本格式的设备树描述,也是我们要编写的设备树源码,一般一个.dts文件对应一个硬件平台。在Linux源码的“arch/arm64/boot/dts/”目录下又根据芯片厂商进行分类。
- DTSI 是指由芯片厂商提供,是同一芯片平台“共用”的设备树文件。
- DTC 是指编译设备树源码的工具,一般情况下我们需要手动安装这个编译工具。
- DTB 是设备树源码编译生成的文件,类似于我们C语言中“.C”文件编译生成“.bin”文件。
- DTBO 是设备树叠加层编译生成的文件,可以对DTB进行叠加补充。
关于设备树的编译
一些基础的通用外设使用U-Boot的设备树来进行初始化,而不通用的部分通过在U-Boot阶段加载内核的设备树来初始化。 所以我们在修改设备树时,主要也是去修改内核的设备树。例如在LubanCat-RK系列的开发过程中, 就没有修改过U-Boot的设备树,直接使用了rk3568-evb板卡的设备树。
注意,设备树的编译需要SDK开发包
# 编译kerneldeb文件
./build.sh kerneldeb
# 编译extboot分区
./build.sh extboot
关于内存管理单元MMU
为了内存防止直接访问物理地址,所以引入MMU的概念,
MMU作用:
- 保护内存: MMU给一些指定的内存块设置了读、写以及可执行的权限,这些权限存储在页表当中,MMU会检查CPU当前所处的是特权模式还是用户模式,如果和操作系统所设置的权限匹配则可以访问,如果CPU要访问一段虚拟地址,则将虚拟地址转换成物理地址,否则将产生异常,防止内存被恶意地修改。
- 提供方便统一的内存空间抽象,实现虚拟地址到物理地址的转换: CPU可以运行在虚拟的内存当中,虚拟内存一般要比实际的物理内存大很多,使得CPU可以运行比较大的应用程序。
那么,访问MMU时会进行内存的访问,而且如果使用了两级的表,那么cpu需要访问3次内存,耗费cpu性能。
所以引入了TLB,TLB对已经访问过的地址进行存储,以便cpu下次访问时,更快回复,若填满,则使用round-robin算法找到一个条目并覆盖此条目。
地址转换函数
上面提到了物理地址到虚拟地址的转换函数。包括ioremap()地址映射和取消地址映射iounmap()函数。
烧录
首先确定文件夹
确定文件夹为sdk文件夹,通常该目录原芯片厂商会提供
一般的:
当然,基本的kernel文件夹 uboot文件夹是必须的
编译
1.u-boot编译
2.内核和设备树
3.recovery 编译
4.文件系统 注意 编译build root 即使用 ./build rootfs 命令
而最后的update.img为最后的需要烧录的文件镜像
此镜像为内核、设备树、文件系统的整合包
好,那么好接下来是 Ubuntu根文件定制
根文件定制
1.首先下载Ubuntu的文件夹 网站 https://cdimage.ubuntu.com/ubuntu-base/releases/20.04.1/release/ubuntu-base-20.04.1-base-amd64.tar.gz
这是20.04的,够用了
2.然后安装qemu 模拟器
apt-get install qemu-user-static
3.然后将刚安装的qemu这个东西 拷贝到刚刚下载的ubuntu文件夹(已解压(tar命令))中,
cp /usr/bin/qemu-aarch64-static system/usr/bin/
(注意,system文件夹中即为我们想要制定的根目录),大概这样:
4.然后准备网络,如下命令
cp -b /etc/resolv.conf system/etc/resolv.conf
//这些命令建议使用sudo
5.接下来我们将使用shell脚本,所以我们需要写一个mount.sh 命名不重要
代码如下
#!/bin/bash
mnt() {
echo "MOUNTING"
sudo mount -t proc /proc ${2}proc
sudo mount -t sysfs /sys ${2}sys
sudo mount -o bind /dev ${2}dev
sudo mount -o bind /dev/pts ${2}dev/pts
}
umnt() {
echo "UNMOUNTING"
sudo umount ${2}proc
sudo umount ${2}sys
sudo umount ${2}dev/pts
sudo umount ${2}dev
}
if [ "$1" == "-m" ] && [ -n "$2" ] ;
then
mnt $1 $2
elif [ "$1" == "-u" ] && [ -n "$2" ];
then
umnt $1 $2
fi
6.给这个脚本权限 并执行该脚本 挂载
chmod +x mount.sh
./mount.sh -m system/
7.进入根文件系统进行操作:
sudo chroot system
8.更新 如果烧录进开发板中依然有网络设备树与驱动,且网络可连接,可以选择不更新
apt update
apt upgrade
9.至少需要安装一个 systemd,否则开机无法进入系统
apt install -y systemd
10.安装自己需要的功能 (可能很慢,不太建议)
apt install vim git ....(根据自己需求添加)
11.安装 xubuntu:
apt-get install xubuntu-desktop
//Q:若出现错误
Unable to locate package xxxx
A:可能是源的问题,替换好的源即可
安装包的源没有添加到 /etc/apt/source.list
中,导致无法识别安装包,可以自行添加源,也可以使用下面给出的 source.list
覆盖原来的 /etc/apt/source.list
文件:
# See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic main restricted
## Major bug fix updates produced after the final release of the
## distribution.
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates main restricted
## Uncomment the following two lines to add software from the 'universe'
## repository.
## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team. Also, please note that software in universe WILL NOT receive any
## review or updates from the Ubuntu security team.
deb http://ports.ubuntu.com/ubuntu-ports/ bionic universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic universe
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-updates universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-updates universe
## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-backports main restricted
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security main restricted
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security universe
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security universe
deb http://ports.ubuntu.com/ubuntu-ports/ bionic-security multiverse
deb-src http://ports.ubuntu.com/ubuntu-ports/ bionic-security multiverse
12.然后是 添加用户:
useradd -s '/bin/bash' -m -G adm,sudo zyl
//zyl 即为用户名
//给用户设置密码:
passwd zyl
13.设置root密码
//给root用户设置密码:
passwd root
//修改完自己的根文件系统就可以退出了。
exit
//一定要取消挂载:
./mount.sh -u system/
制作根文件
制作自己的根文件系统,大小依据自己的根文件系统而定,注意依据 temp
文件夹的大小来修改 count
值:
mkdir rootfs
dd if=/dev/zero of=rootfs.img bs=1M count=4000
mkfs.ext4 rootfs.img
sudo mount rootfs.img rootfs/
sudo cp -rfp system/* rootfs/
sudo umount rootfs/
e2fsck -p -f rootfs.img
resize2fs -M rootfs.img
rootfs.img为最终的根文件系统镜像文件
驱动正文
驱动结构
1.头文件
2.驱动加载函数
3.驱动卸载函数
4.许可证声明
5.模块参数
6.版本信息
以上即在XXX.c的c驱动文件中的
如图:
驱动操作
加载驱动
insmod xxx.ko
查看驱动
lsmod
卸载驱动
rmmod xxx.ko
加载驱动及其依赖
modprobe xxx.ko
驱动结构 (附加)——驱动传参
驱动支持传递参数类型:
基本类型:char bool int long short byte ushort uint
数组 array
字符串 string
如何传递?
驱动传递基本类型、传递数组、传递字符串,对应函数
module_param :传递基本类型参数
module_param_array:传递数组类型函数
module_param_string :传递字符串类型函数
以上三个传参函数在include/linux/moduleparam.h中有定义
一、传递基本类型参数函数
对于perm 参数的读写权限
读写权限在include/linux/stat.h和include/uapi/linux/stat.h
R:可读权限 W:可写权限 X:可执行权限 U(USR):指拥有者
G(GRP) :指同组用户 O(OTH):与文件拥有者不同组的
例子:S_IRWXU S_I不用看 RWX 指可读可写可执行 U指对拥有者操作
对于右侧的; S_IRWXUGO S_I不用看 RWX 指可读可写可执行 UGO 指对文件拥有者、同组者、以及与文件拥有者不同组的 即所有用户拥有可读可写可执行权限
常用为S_IRUGO 即所有用户拥有可读权限
二、传递数组类型函数
三、传递字符串类型参数给驱动
附 :描述模块参数函数
配置VSCODE环境
因为我们使用vscode编写,需要配置vscode环境,以便我们使用时有代码补全
之前如果出现vscode出现引入头文件出错的情况也可以根据此处进行相应配置
vscode 隐藏顶部标题栏
修改方法:
- 打开vscode的设置(File - Preferences - settings),搜索
window.titlebarstyle
, 修改为custom
,重启vscode即可生效:
一般需要引入的头文件位置在 sdk开发包/kernel内核包/include文件夹/linux文件夹
查看绝对路径
然后在需要的工程使用快捷键 CTRL+shift+P,选择C/C++ 编辑配置(json)
在includepath 下加一行即可路径即可,保存 (注意两个includepath 之间有逗号的)
例子
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
static int a=0;
static int array[5]={0};
static int array_size;
static char str1[10]={0};
module_param(a,int,S_IRUGO);
MODULE_PARM_DESC(a,"e.g a=1");
module_param_array(array,int,&array_size,S_IRUGO);
MODULE_PARM_DESC(array,"e.g array=1,2,3");
module_param_string(str,str1,sizeof(str1),S_IRUGO);//注意,此处str为输入的名字,如果使用驱动输入没有该字符串,则无效
MODULE_PARM_DESC(str,"e.g str=hello");
static int moduleparam_init(void)
{
int i=0;
printk("a id %d\n",a);
printk("helloworld\n");
for (i = 0; i < array_size; i++)
{
printk("array[%d] is %d\n",i,array[i]);
}
printk("array_size is %d\n",array_size);
printk("str is %s\n",str1);
return 0;
}
static void moduleparam_exit(void)
{
printk("bye bye\n");
}
module_init(moduleparam_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
如果打印没有输出
则输入
dmesg
//以查看
内核符号表的导出
符号表是方便各个驱动模块相互调用的,所有驱动模块可以查看或向符号表中写入,同时也可以通过自己所需找到符号表对应的驱动函数。
符号表使用
在啊a.c中
int add(int a,int b)
{
return a+b;
}
EXPORT_SYMBOL(add);//此处表示
生成后有一个Modules.symvers文件,放到b.c中
然后在b.c中引入外部函数
extern int add(int a,int b);
然后生成b.ko使用即可,需要注意的是安装这两个模块时,需要先安装a模块,然后安装b模块,卸载时相反,先卸载b模块再卸载a模块
注意编译顺序,也是先编译依赖的。*编译、安装、卸载的顺序很重要
编译驱动方法(驱动程序)的两种形式
一、第一种编译方法
将驱动放在Linux内核中,编译linux内核,生成镜像后,烧录内核镜像
二、第二种编译方法
将驱动编译成内核模块,独立于Linux内核,也需要先将内核文件(.ko)先放到板卡中,且方便调试
编译内核模块
关于Makefile文件
KERNEL_DIR=/home/zyl/ker_y/kernel-stable-4.19-rk356x
内核源码路径
ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
export ARCH CROSS_COMPILE
obj-m := helloworld.o
out = helloworld_out
//helloworld.o编译过程文件,
//helloworld_out 即为最后输出的 主要为 下面$(CROSS_COMPILE)gcc -o $(out) main.c 这一行服务的
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules //主要代码
$(CROSS_COMPILE)gcc -o $(out) main.c //为了方面调试可以在此文件夹写
//main文件生成可执行文件方便调试
.PHONY:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
rm $(out)
字符设备基础
关于主设备号与次设备号
主设备号表示某一类驱动如 USB CAN …
而此设备号表示某个设备 如USB中的鼠标或键盘 CAN0 或CAN1 等
一般写驱动时要包含types.h头文件
然后可以看到设备号时dev_t 而且还是32位,其中高12位表示主设备号,低20位表示次设备号
linux定义了宏,方便我们快速获取主设备号和次设备号以及 主从设备号整合
设备号分配
分为静态分配和动态分配
静态分配 :到/proc/devices 查看,选择没有的设备号,可以使用
动态分配:系统自动分配
静态分配函数
动态分配(注册)设备号(建议使用)
关于释放设备号
例子:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
// #include <linux/kernel.h>
// #include <linux/errno.h>
// #include <linux/types.h>
// #include <linux/proc_fs.h>
// #include <linux/fcntl.h>
static int major=0;
static int minor=0;
module_param(major,int,S_IRUGO);
module_param(minor,int,S_IRUGO);
dev_t dev_num;
static int moduleparam_init(void)
{
int ret;
if (major)
{
printk("major: %d, minor: %d\n", major, minor);
dev_num=MKDEV(major,minor);
ret=register_chrdev_region(dev_num,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
printk("register_chr_dev is ok\n");
}
else //donmg tai shen qing
{
ret=alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
printk("register_chr_dev is ok\n");
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk("major: %d, minor: %d\n", major, minor);
}
return 0;
}
static void moduleparam_exit(void)
{
unregister_chrdev_region(dev_num,1);
printk("bye bye\n");
}
module_init(moduleparam_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
加载使用 sudo rmmod dev_t.ko 时
为动态加载,内核帮我们分配设备号
加载使用 sudo insmod dev_t.ko major=222 minor=0 时,为静态加载,指定主设备号222 从设备号0,但要保证两个号与已有不重复 (通过cat /proc/devices 查看已有设备设备号)
设备号注册
*注意,file_operations 结构体 为文件操作结构体 系统调用和驱动程序的桥梁
向系统中添加一个设备 多个可以改变count变量
以及 cdev_del(dev_t ) 删除系统中设备号的对应设备
所以通常流程为 注册、init初始化、add添加,最后若不使用则cdev_del
例程:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
dev_t dev_num;
struct cdev cdev_test;
struct file_operations cdev_test_ops={
.owner=THIS_MODULE
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
printk("register_chr_dev is ok\n");
cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&cdev_test,&cdev_test_ops);
cdev_add(&cdev_test,dev_num,1);
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&cdev_test); //xian
unregister_chrdev_region(dev_num,1);//hou
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
file_operations 结构体 ⭐
例子:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
dev_t dev_num;
struct cdev cdev_test;
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk("This is read\n");
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk("This is write\n");
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
printk("This is release\n");
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
printk("register_chr_dev is ok\n");
cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&cdev_test,&cdev_test_ops);
cdev_add(&cdev_test,dev_num,1);
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&cdev_test); //xian
unregister_chrdev_region(dev_num,1);//hou
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
设备节点概念
创建设备节点
设备节点即应用程序于驱动程序的桥梁
自动创建即mdev创建
//app应用程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd ;
char buf[64];
fd=open("/dev/test",O_RDWR);
if (fd < 0)
{
perror("open error");
return fd;
}
close(fd);
return 0;
}
Makefile文件
//然后是Makefile文件,因为需要编译应用程序
KERNEL_DIR=/home/zyl/ker_y/kernel-stable-4.19-rk356x#/home/zyl/sdk/RK356X_NVR_LINUS/kernel
ARCH=arm64
CROSS_COMPILE=aarch64-linux-gnu-
export ARCH CROSS_COMPILE
obj-m := file.o
out = test_app
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
$(CROSS_COMPILE)gcc -o $(out) app.c
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
//注意,app.c 要改为已存在的app应用程序名字,输出则为out即test_app
具体操作
查看文件,然后加载ko文件
使用dmesg 查看信息
主设备号235,从设备号0
创建节点于/dev下
创建后就可以看到了
然后执行我们的app
有了, 即对应
自动创建设备节点
那么我们只需要在/sys/clas/下存在对应文件夹,就能让udev(mdev)自动为我们创建设备节点,那么如何在/sys/clas/下生产对应文件夹。
例:class = class_create(THIS_MODULE,“test”);
除了创建该class后,还有在该class下创建设备,使用device_create
例: device= device_create(class,NULL,dev_num,NULL,“test”);
以及
例子:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
dev_t dev_num;
static int major=0;
static int minor=0;
struct cdev cdev_test;
struct class *class;
struct device *device;
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
printk("This is read\n");
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
printk("This is write\n");
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)//ji close function
{
printk("This is release\n");
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk("major: %d, minor: %d\n", major, minor);
cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&cdev_test,&cdev_test_ops);
cdev_add(&cdev_test,dev_num,1);
class = class_create(THIS_MODULE,"test"); // test: /sys下class文件夹下的文件夹名字
device= device_create(class,NULL,dev_num,NULL,"test");//test :设备节点名字
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&cdev_test); //xian
unregister_chrdev_region(dev_num,1);//hou
device_destroy(class,dev_num);
class_destroy(class);
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
内核空间与用户空间
内核空间和用户空间的数据交换
APP程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd ;
char buf[64];
char buf1[32];
char buf2[32]="nihao";
fd=open("/dev/test",O_RDWR);
if (fd < 0)
{
perror("open error");
return fd;
}
read(fd,buf1,sizeof(buf1));
printf("buf1 is %s\n",buf1);
write(fd,buf2,sizeof(buf2));
close(fd);
return 0;
}
/// 驱动文件
、
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
dev_t dev_num;
static int major=0;
static int minor=0;
struct cdev cdev_test;
struct class *class;
struct device *device;
static int cdev_test_open(struct inode *inode, struct file *file)
{
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
char kbuf[32]="This is cdev_test_read";
if(copy_to_user(buf, kbuf, strlen(kbuf))!=0)
{
printk("copy_to_user error");
return -1;
}
printk("This is read\n");
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
// printk("This is write\n");
char kbuf[32]={0};
if(copy_from_user(kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("kbuf: %s\n", kbuf);
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)//ji close function
{
printk("This is release\n");
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
major=MAJOR(dev_num);
minor=MINOR(dev_num);
printk("major: %d, minor: %d\n", major, minor);
cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&cdev_test,&cdev_test_ops);
cdev_add(&cdev_test,dev_num,1);
class = class_create(THIS_MODULE,"test");
device= device_create(class,NULL,dev_num,NULL,"test");
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&cdev_test); //xian
unregister_chrdev_region(dev_num,1);//hou
device_destroy(class,dev_num);
class_destroy(class);
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
关于详细原理
以驱动最为参考点分为 从用户区拷贝 和 拷贝到用户区
先看 1.从用户区拷贝 copy_from_user
一般在驱动write关联函数中
起点为 app应用程序的write函数 (#?图1)
调用后到 驱动关联(#?图2)
到达此处(#?图3)
详细说明: 图1传输数据的变量为buf2,以及长度
到达图3后;图1的buf2变为图3的buf,图1的sizeof(buf2)变为图3的size,然后copy_from_user函数将传输的数据放到kbuf中,以此实现从用户到内核的数据传输。
然后是2. 拷贝到用户区 copy_to_user
一般在驱动read关联函数中
起点为 app应用程序的read函数 (#?图1)
调用后到 驱动关联(#?图2)
到达此处(#?图3)
详细说明: 图1传输数据的变量为buf1,以及长度
到达图3后;图1的buf1变为图3的buf,图1的sizeof(buf1)变为图3的size,然后copy_to_user函数将kbuf,长度为strlen(kbuf)传输到buf中,完成后回到主函数打印即可,以此实现从内核空间到用户空间的数据传输。
使用文件私有数据
可以看到在驱动中存在一些公共变量,那么我们可以将所有的公共变量放到一个结构体中,方便其他函数调用,该结构体就叫设备结构体了
例:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
struct device_test
{
dev_t dev_num;
int major;
int minor;
struct cdev cdev_test;
struct class *class;
struct device *device;
char kbuf[32];
};
struct device_test dev1;
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if(copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf))!=0)
{
printk("copy_to_user error");
return -1;
}
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if(copy_from_user(test_dev->kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("test_dev->kbuf: %s\n",dev1.kbuf);
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)//ji close function
{
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name");
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
dev1.major=MAJOR(dev1.dev_num);
dev1.minor=MINOR(dev1.dev_num);
printk("major: %d, minor: %d\n", dev1.major, dev1.minor);
dev1.cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&dev1.cdev_test,&cdev_test_ops);
cdev_add(&dev1.cdev_test,dev1.dev_num,1);
dev1.class = class_create(THIS_MODULE,"test");
dev1.device= device_create(dev1.class,NULL,dev1.dev_num,NULL,"test");
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&dev1.cdev_test); //xian
unregister_chrdev_region(dev1.dev_num,1);//hou
device_destroy(dev1.class,dev1.dev_num);
class_destroy(dev1.class);
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
适用场景:同一个驱动不同设备,如都是can驱动,但是有两路can,假定一路CAN0 一路CAN1
即兼容主设备号相同,而次设备号不同
所以适用不同此设备驱动如下
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
struct device_test
{
dev_t dev_num;
int major;
int minor;
struct cdev cdev_test;
struct class *class;
struct device *device;
char kbuf[32];
};
struct device_test dev1;
struct device_test dev2;
static int cdev_test_open(struct inode *inode, struct file *file)
{
dev1.minor=0;
dev2.minor=1;
file->private_data=container_of(inode->i_cdev,struct device_test,cdev_test);
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if(copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf))!=0)
{
printk("copy_to_user error");
return -1;
}
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if (test_dev->minor==0)
{
if(copy_from_user(test_dev->kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("test1_dev->kbuf: %s\n",test_dev->kbuf);
}
else if (test_dev->minor==1)
{
if(copy_from_user(test_dev->kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("test2_dev->kbuf: %s\n",test_dev->kbuf);
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)//ji close function
{
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev1.dev_num,0,2,"chrdev_name");///2
if (ret<0)
{
printk("error: register_chrdev_region\n");
}
dev1.major=MAJOR(dev1.dev_num);
dev1.minor=MINOR(dev1.dev_num);
printk("major: %d, minor: %d\n", dev1.major, dev1.minor);
dev1.cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&dev1.cdev_test,&cdev_test_ops);
cdev_add(&dev1.cdev_test,dev1.dev_num,1);
dev1.class = class_create(THIS_MODULE,"test");
dev1.device= device_create(dev1.class,NULL,dev1.dev_num,NULL,"test1");
dev2.major=MAJOR(dev1.dev_num+1);
dev2.minor=MINOR(dev1.dev_num+1);
printk("major: %d, minor: %d\n", dev2.major, dev2.minor);
dev2.cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&dev2.cdev_test,&cdev_test_ops);
cdev_add(&dev2.cdev_test,dev1.dev_num+1,1);
dev2.class = class_create(THIS_MODULE,"test2");
dev2.device= device_create(dev2.class,NULL,dev1.dev_num+1,NULL,"test2");
return 0;
}
static void moduleparam_exit(void)
{
cdev_del(&dev1.cdev_test); //xian
cdev_del(&dev2.cdev_test);
unregister_chrdev_region(dev1.dev_num,1);//hou
unregister_chrdev_region(dev1.dev_num+1,1);//hou
device_destroy(dev1.class,dev1.dev_num);
device_destroy(dev2.class,dev1.dev_num+1);
class_destroy(dev1.class);
class_destroy(dev2.class);
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
附应用程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
char buf1[32]="nihao /dev/test1";
char buf2[32]="nihao /dev/test2";
fd1=open("/dev/test1",O_RDWR);
if (fd1 < 0)
{
perror("open error");
return fd1;
}
write(fd1,buf1,sizeof(buf1));
close(fd1);
fd2=open("/dev/test2",O_RDWR);
if (fd2 < 0)
{
perror("open error");
return fd1;
}
write(fd2,buf2,sizeof(buf2));
close(fd2);
return 0;
}
杂项设备驱动(未完整)
主设备号固定10, 次设备号不可重复,不需要自己创建节点,会自动创建节点,
Linux驱动错误处理
之前错误处理方式都是判断返回值,连return都没有,在这里来进行处理一下
先看这个:
在这里有alloc_chrdev_region函数 申请设备号没有成功,申请成功标志位为ret
一般错误处理使用goto 语句
而且goto语句的
以及错误码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
struct device_test
{
dev_t dev_num;
int major;
int minor;
struct cdev cdev_test;
struct class *class;
struct device *device;
char kbuf[32];
};
struct device_test dev1;
struct device_test dev2;
static int cdev_test_open(struct inode *inode, struct file *file)
{
dev1.minor=0;
dev2.minor=1;
file->private_data=container_of(inode->i_cdev,struct device_test,cdev_test);
printk("This is open\n");
return 0;
}
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if(copy_to_user(buf, test_dev->kbuf, strlen(test_dev->kbuf))!=0)
{
printk("copy_to_user error");
return -1;
}
return 0;
}
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test*)file->private_data;
if (test_dev->minor==0)
{
if(copy_from_user(test_dev->kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("test1_dev->kbuf: %s\n",test_dev->kbuf);
}
else if (test_dev->minor==1)
{
if(copy_from_user(test_dev->kbuf, buf, size)!=0)
{
printk("copy_form_user error");
return -1;
}
printk("test2_dev->kbuf: %s\n",test_dev->kbuf);
}
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)//ji close function
{
return 0;
}
struct file_operations cdev_test_ops={
.owner=THIS_MODULE,
.open=cdev_test_open,
.read=cdev_test_read,
.write=cdev_test_write,
.release=cdev_test_release
};
static int modulecdev_init(void)
{
int ret;
ret=alloc_chrdev_region(&dev1.dev_num,0,2,"chrdev_name");///2
if (ret<0)
{
printk("error: register_chrdev_region\n");
goto err_chrdev;
}
dev1.major=MAJOR(dev1.dev_num);
dev1.minor=MINOR(dev1.dev_num);
printk("major: %d, minor: %d\n", dev1.major, dev1.minor);
dev1.cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&dev1.cdev_test,&cdev_test_ops);
cdev_add(&dev1.cdev_test,dev1.dev_num,1);
if (ret<0)
{
goto err_chr_add;
}
dev1.class = class_create(THIS_MODULE,"test");
if (IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_class_create;
}
dev1.device= device_create(dev1.class,NULL,dev1.dev_num,NULL,"test1");
if (IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.class);
goto err_device_create;
}
dev2.major=MAJOR(dev1.dev_num+1);
dev2.minor=MINOR(dev1.dev_num+1);
printk("major: %d, minor: %d\n", dev2.major, dev2.minor);
dev2.cdev_test.owner=THIS_MODULE;//SUOCHU MO KUAI
cdev_init(&dev2.cdev_test,&cdev_test_ops);
cdev_add(&dev2.cdev_test,dev1.dev_num+1,1);
dev2.class = class_create(THIS_MODULE,"test2");
dev2.device= device_create(dev2.class,NULL,dev1.dev_num+1,NULL,"test2");
return 0;
err_device_create:
class_destroy(dev1.class);
err_class_create:
cdev_del(&dev1.cdev_test);
err_chr_add:
unregister_chrdev_region(dev1.dev_num,1);
err_chrdev:
return ret;
}
static void moduleparam_exit(void)
{
cdev_del(&dev1.cdev_test); //xian
cdev_del(&dev2.cdev_test);
unregister_chrdev_region(dev1.dev_num,1);//hou
unregister_chrdev_region(dev1.dev_num+1,1);//hou
device_destroy(dev1.class,dev1.dev_num);
device_destroy(dev2.class,dev1.dev_num+1);
class_destroy(dev1.class);
class_destroy(dev2.class);
printk("bye bye\n");
}
module_init(modulecdev_init);
module_exit(moduleparam_exit);
MODULE_LICENSE("GPL");//XIE YI
MODULE_AUTHOR("zyl_param_test");
MODULE_VERSION("V1.0");//version
点亮LED灯
查看底板原理图,user-led的IO口对应为GPIO4_D2
配置GPIO
1.复用关系
2.方向(输出or输入)
3.数据
复用:
关系复用基地址 0xFDC60000
要操作的地址 0xFDC60000+0x0078
即 0xfdc60078 对应的4d2的值应为0,即不复用,使用gpio功能
方向:
GPIO基地址 0xFE770000
方向J寄存器基地址 0xFE770000 +0x000c
对应4d2的值为1 即输出方向
数据:
数据寄存器地址:0xFE770000
数据寄存器偏移 0x0004
即 0xfe770004
当值为0x04000401 为输出1 对应原理图为灭
当值为0x04000001 为输出0 对应原理图为亮
详细流程如下
找到(led)端口 GPIO4_D2
我们控制led的亮灭就是控制它的io输出
对于一般的io控制,我们需要配置:复用、方向和数据
复用即复用类型,是io还是其他接口
如何查找?
找到第三章FunctionDescription
因为GPIO4,在此章节下PMU表格下搜索GPIO4会找到GRF_GPIO4D_IOMUX_L 表格该列有偏移地址 0x0078
向上看,会看到在SYS_GRF Register Description下
然后到第三章最开头的表格查看基地址,即SYS_GRF的基地址 0xFDC60000
然后是方向配置
到十六章GPIO章节,查看寄存器摘要表格
基地址在第一章Address Mapping中查找 即0xFE770000
其中GPIO_SWPORT_DDR_L即方向寄存器低位寄存器
GPIO_SWPORT_DDR_H为高位的
如何选择高还是低
我们端口是GPIO4_D2
其中GPIO有4组,每组有ABCD四个小组,每个小组有8个端口。
我们是D组(AB为低CD为高)
所以用高位寄存器的偏移地址 0x000C
注意,方向寄存器写入时需要注意该寄存器的高位是对应控制写入的使能端,
写入数据时要对应写入使能写1
最后是数据寄存器
在方向寄存器上面有 GPIO_SWPORT_DR_L 即方向寄存器的低位控制寄存器
同样基地址为0xFE770000,与配置方向时的基地址一样
高位和低位选择和配置方向的方法一样
偏移 即 0x0004
附:查看、配置与控制亮led与灭led
接下来是使用代码进行控制led灯的亮灭
设备树
什么是设备树,为什么引入设备树
也是描述硬件资源文件
什么是设备树?
设备树是描述硬件的文本文档,因为语法结构像树一样,所以叫设备树。
dtc编译器源码文件夹 sdk/kernel/scripts/dtc
内若没有dtc可执行文件,有dtc源码
打开kernel的.config文件,/CONFIG_DTC 令其等于y如图即可
DTC编译器使用
dtc -I dts -O dtb -o xxx.dtb xxx.dts //xxx.dtb 输出dtb文件名 xxx.dts 输入dts文件名
dtc -I dtb -O dts -o xxx.dts xxx.dtb
设备树基本语法
1.设备根节点
2.设备子节点
3.节点名称
注意,名称为必选项
例:
/dts-v1/;
/{
node1{
node1{
};
};
node2{
node1-child{
};
};
led:gpio@20020101{
};
};
编译dtb会报错,
因为 除此之外,还需要有reg书写
4.reg属性
除此之外还需要address-cells和size-cells属性决定,如图
举例:
led:gpio@20020101{
#address-cells=<1>;//表示向下子节点内有一个地址
#size-cells=<1>;//表示向下子节点内有一个长度值
node1-child
{
reg=<0x2200000 0x4000>;//所以0x2200000 为地址,0x4000为长度
};
};
若
led:gpio@20020101{
#address-cells=<2>;//表示向下子节点内有两个地址
#size-cells=<1>;//表示向下子节点内有一个长度值
node1-child
{
reg=<0x2200000 0x4001 0x4002>;//所以0x2200000为地址,0x4002也为地址,0x4001为长度
};
};
5.model属性
6.status属性
7.compatible属性
8.设备树特殊节点
用于定义别名 该方法可用于快速起别名排序
高阶字符驱动
IO模型
io模型有阻塞io 非阻塞io,信号驱动io,io多路复用,异步io;前四个为同步io
中断
中断控制器 GIC
中断控制器也可以级联
中断控制器内部有 仲裁器 向cpu连接的中断IRQ
另外 中断号
中断源类型
等待事件
例:
。。。(未进行到此处,或项目计划已更改)
IO多路复用
poll和select基本一样,监听多个文件,通过轮询获取准备好的文件描述符
而epoll是主动轮询变为被动通知,当事件发生时,被动接收通知
从代码角度可以更快速的实现poll功能,
poll函数介绍
以及pollfd结构体
例: 18_poll例程,加载驱动 执行a.out和b.out ,实现poll阻塞执行… 未实际完成(只有单命令框)
。。。(未进行到此处,或项目计划已更改)
Linux 野火-鲁班猫End—————————————↑
附 PDF链接
链接: https://pan.baidu.com/s/1wherS5m60j2X10K8JVWjow?pwd=yyds 提取码: yyds 复制这段内容后打开百度网盘手机App,操作更方便哦