🐧🐧🐧🐧🐧🐧🐧🐧🐧🐧🐧
一些缩写
.elf 文件:Executable and Linkable Format,可执行连接格式
编译(compilation)指令:
gcc -S hello.c -o hello.s
汇编(assembly)指令:
gcc -c hello.s -o hello.o
信号量:semaphore
misc:文件位于drivers\char\misc.c msic意思是杂项。
因此 MISC 驱动也叫做杂项驱动,也就是当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。 MISC 驱动其实就是最简单的字符设备驱动,通常嵌套在 platform 总线驱动中,实现复杂的驱动,
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号。随着 Linux字符设备驱动的不断增加,设备号变得越来越紧张,尤其是主设备号, MISC 设备驱动就用于解决此问题。 MISC 设备会自动创建 cdev,不需要像我们以前那样手动创建,因此采用 MISC 设备驱动可以简化字符设备驱动的编写。
IOMUX是“Input/Output Multiplexer and Unifier”的缩写,中文可以翻译为“输入/输出多路复用器和统一器”。它是一种电子设备或电路,用于在多个信号源和目标之间进行选择和转换。
在具体应用中,IOMUX通常用于处理电子设备中的信号输入和输出。它可以将多个信号源(如不同的传感器或数据源)与多个目标(如不同的处理器或输出设备)进行连接和分配,从而实现信号的复用和转换。
关键词解释:
- Input/Output(输入/输出):指设备或系统接收和发送数据的接口。
- Multiplexer(多路复用器):一种设备或技术,可以将多个信号或数据流合并为一个信号流进行传输或处理。
- Unifier(统一器):用于将不同来源或格式的数据、信号等统一处理或输出的设备或技术。
IOMUX在许多电子设备和系统中都有应用,如嵌入式系统、微控制器、FPGA等,它可以帮助提高系统的效率和性能,同时减少硬件的复杂性和成本。
一些C语法
c语言中将int变量转换为void *指针或int *指针
在C语言中,将一个整型变量强制转换为void *指针类型通常是为了将整数值作为一个通用指针传递给函数,或者在特定的上下文中使用这个整数的值作为内存地址。进行这样的转换可以用类型转换运算符 (void *)。以下是一个简单的示例:
1 | int integerValue = 12345; |
在这个例子中,整型变量integerValue的值被转换为一个intptr_t类型,然后再转换为void *类型。intptr_t是一种整数类型,它的大小足够存放指针,这保证了转换过程中不会丢失信息。这种转换方法是可移植性较好的,因为它考虑到了不同平台上指针和整数大小可能不一致的情况。
需要注意的是,这样的转换并不意味着整数真的变成了一个有效的内存地址。如果你打算通过这样的指针去访问内存,必须非常小心,确保这个整数值确实对应于一个合法且可访问的内存地址,否则会导致未定义行为,比如程序崩溃或者数据损坏。在多线程和异步编程中,将整数简单地作为数据通过指针传递给线程函数是一种常见且安全的用途。
于是想到是否解引用pointerValue的值,发现是不可以的。目前暂时不知道将int 变量转换为void指针的目的是什么
1 |
|
这一部分问题在于:
将一个整数直接转换为指针并解引用该指针,这通常会导致未定义行为(undefined behavior)。因为在现代操作系统中,这样的指针几乎不可能指向合法的内存位置,因此解引用它的结果是不可预测的,可能会导致程序崩溃、数据损坏或安全漏洞。
正确的做法是确保指针总是指向有效的、已分配的内存位置。对于测试的目的,我们应该构造一个安全的环境来模拟和验证逻辑,而不是直接执行可能导致未定义行为的操作。
一些有用的命令
查看工具链中头文件和库文件的路径echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v -
搜索某一个字符grep "prefix" * -nwr
Makefile
Makefile的引入及规则
使用keil, mdk,avr等工具开发程序时点击鼠标就可以编译了,它的内部机制是什么?它怎么组织管理程序?怎么决定编译哪一个文件?
答:实际上windows工具管理程序的内部机制,也是Makefile,我们在linux下来开发裸板程序的时候,使用Makefile组织管理这些程序,本节我们来讲解Makefile最基本的规则。Makefile要做什么事情呢?
组织管理程序,组织管理文件,我们写一个程序来实验一下:
文件a.c
1 | 02 |
文件b.c
1 | 2 |
编译:
1 | gcc -o test a.c b.c |
运行:
1 | ./test |
结果:
1 | This is B |
gcc -o test a.c b.c 这条命令虽然简单,但是它完成的功能不简单。
我们来看看它做了哪些事情,
我们知道.c程序 ==》 得到可执行程序它们之间要经过四个步骤:
- 1.预处理
- 2.编译
- 3.汇编
- 4.链接
我们经常把前三个步骤统称为编译了。我们具体分析:gcc -o test a.c b.c这条命令
它们要经过下面几个步骤:
- 1)对于a.c:执行:预处理 编译 汇编 的过程,a.c ==>xxx.s ==>xxx.o 文件。
- 2)对于b.c:执行:预处理 编译 汇编 的过程,b.c ==>yyy.s ==>yyy.o 文件。
- 3)最后:xxx.o和yyy.o链接在一起得到一个test应用程序。
提示:gcc -o test a.c b.c -v :加上一个‘-v’选项可以看到它们的处理过程,
第一次编译 a.c 得到 xxx.o 文件,这是很合乎情理的, 执行完第一次之后,如果修改 a.c 又再次执行:gcc -o test a.c b.c,对于 a.c 应该重新生成 xxx.o,但是对于 b.c 又会重新编译一次,这完全没有必要,b.c 根本没有修改,直接使用第一次生成的 yyy.o 文件就可以了。
缺点:对所有的文件都会再处理一次,即使 b.c 没有经过修改,b.c 也会重新编译一次,当文件比较少时,这没有没有什么问题,当文件非常多的时候,就会带来非常多的效率问题如果文件非常多的时候,我们,只是修改了一个文件,所用的文件就会重新处理一次,编译的时候就会等待很长时间。
对于这些源文件,我们应该分别处理,执行:预处理 编译 汇编,先分别编译它们,最后再把它们链接在一次,比如:
编译:
1 | gcc -o a.o a.c |
链接:
1 | gcc -o test a.o b.o |
比如:上面的例子,当我们修改a.c之后,a.c会重现编译然后再把它们链接在一起就可以了。b.c
就不需要重新编译。
那么问题又来了,怎么知道哪些文件被更新了/被修改了?
比较时间:比较 a.o 和 a.c 的时间,如果a.c的时间比 a.o 的时间更加新的话,就表明 a.c 被修改了,同理b.o和b.c也会进行同样的比较。比较test和 a.o,b.o 的时间,如果a.o或者b.o的时间比test更加新的话,就表明应该重新生成test。Makefile
就是这样做的。我们现在来写出一个简单的Makefile:
makefie最基本的语法是规则,规则:
1 | 目标 : 依赖1 依赖2 ... |
当“依赖”比“目标”新,执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:
1 | test :a.o b.o //test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比test新的时候, |
我们来作一下实验:
在改目录下我们写一个Makefile文件:
文件:Makefile
1 | 1 test:a.o b.o |
上面是makefile中的三条规则。makefile,就是名字为“makefile”的文件。当我们想编译程序时,直接执行make命令就可以了,一执行make命令它想生成第一个目标test可执行程序,
如果发现a.o 或者b.o没有,就要先生成a.o或者b.o,发现a.o依赖a.c,有a.c但是没有a.o,他就会认为a.c比a.o新,就会执行它们下面的命令来生成a.o,同理b.o和b.c的处理关系也是这样的。
如果修改a.c ,我们再次执行make,它的本意是想生成第一个目标test应用程序,它需要先生成a.o,发现a.o依赖a.c(执行我们修改了a.c)发现a.c比a.o更加新,就会执行gcc -c -o a.o
a.c命令来生成a.o文件。b.o依赖b.c,发现b.c并没有修改,就不会执行gcc -c -o b.o
b.c来重新生成b.o文件。现在a.o b.o都有了,其中的a.o比test更加新,就会执行 gcc -o
test a.ob.o来重新链接得到test可执行程序。所以当执行make命令时候就会执行下面两条执行:
1 | gcc -c -o a.o a.c |
我们第一次执行make的时候,会执行下面三条命令(三条命令都执行):
1 | gcc -c -o a.o a.c |
再次执行make 就会显示下面的提示:
1 | make: `test' is up to date. |
我们再次执行make就会判断Makefile文件中的依赖,发现依赖没有更新,所以目标文件就不会重现生成,就会有上面的提示。当我们修改a.c后,重新执行make,
就会执行下面两条指令:
1 | gcc -c -o a.o a.c |
我们同时修改a.c b.c,执行make就会执行下面三条指令。
1 | gcc -c -o a.o a.c |
a.c文件修改了,重新编译生成a.o, b.c修改了重新编译生成b.o,a.o,b.o都更新了重新链接生成test可执行程序,makefile的规则其实还是比较简单的。规则是Makefie的核心,
执行make命令的时候,就会在当前目录下面找到名字为:Makefile的文件,根据里面的内容来执行里面的判断/命令。
shell pwd 和PWD的区别
pwd:是一条命令,可以在makefile文件中引用(shel pwd)来获取当前目录
PWD:是一个变量,可以直接echo $PWD来获取当前目录
经过测试echo pwd
和echo $PWD结果是一样的,推测可以在makefile文件中替换使用pwd
$PWD $(shell pwd)
一个shell pwd的例子,这样的例子很常见
1 | INCDIR := $(shell pwd) //将shell pwd作为变量,通过 $(shell pwd)形式引用这个变量 |
文件IO
标准io和系统io的区别在于标准io引入了一个用户buffer,系统io没有。
系统io
文件句柄:fd = 0 1 2 3
文件句柄如何和具体文件挂钩?
0 -> /dev/pts/0 代表标准输入
1 -> /dev/pts/0 代表标准输出
2 -> /dev/pts/0 代表标准错误
3 -> /home/book/01_all_series_quickstart/04_嵌入式Linux应用开发基础知识/source/06_fileio/01_open/1.txt
代表自己打开的文件
open 函数
errno: 错误码,可以配合strerror(errno)来打印错误信息,效果同perror
perror(“open error”)//用来打印错误信息
O_RDONLY: 以只读方式打开文件。
O_WRONLY: 以只写方式打开文件。
O_RDWR: 以读写方式打开文件。
O_CREAT: 如果文件不存在,则创建之。以默认权限创建,最终的权限由mode和umask决定。
O_EXCL: 当与O_CREAT一起使用时,如果文件已存在则打开失败。
O_TRUNC: 打开文件后将其长度截断为0。
O_APPEND: 写入时,数据将被追加到文件末尾。
int open(const char *pathname, int flags);
1)使用open创建一个不存在的文件
1
fd =open(argv[1],O_CREAT|O_RDWR)
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
交叉编译 freetype
1.编译程序时去哪找头文件
1)系统目录:交叉编译工具链的某个include 目录
2)自行指定:使用 -I dir 来指定
2.链接时去哪找库文件
1)系统目录:交叉编译工具链的某个lib 目录
2)自行指定:使用 -L dir 来指定
3.运行时去哪找库文件
1)系统目录:板子上的/lib /usr/lib 目录
2)自行指定:使用 LD_LIBRARY_PATH 来指定
4.运行时不需要头文件
/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/include
LIBRARY_PATH=/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/arm-buildroot-linux-gnueabihf/sysroot/usr/lib/
输入设备
内核中使用input_dev结构体来描述一个输入设备。
1 | //D:\Linux-4.9.88\include\linux\input.h |
内核使用input_event结构体来描述“输入事件”
1 | //include\uapi\linux\input.h |
当一个事件上传完之后,会上报一个“同步事件”,表示数据上传完毕。同步事件也是一个input_event结构体,type = EV_SYN,code = SYN_REPORT,value = 0.即0 0 0
查看有哪些输入设备:
1
2
3
4
5
6
7
8
9
10
11ls /dev/input/* -l
crw-rw---- 1 root input 13, 64 Jan 1 00:00 /dev/input/event0
crw-rw---- 1 root input 13, 65 Jan 1 00:00 /dev/input/event1
crw-rw---- 1 root input 13, 66 Jan 1 00:00 /dev/input/event2
crw-rw---- 1 root input 13, 63 Jan 1 00:00 /dev/input/mice//鼠标
/dev/input/by-path:
total 0
lrwxrwxrwx 1 root root 9 Jan 1 00:00 platform-20cc000.snvs:snvs-powerkey-event -> ../event0
lrwxrwxrwx 1 root root 9 Jan 1 00:00 platform-gpio-keys-event -> ../event2
获取与 event 对应的相关设备信息
1 | cat /proc/bus/input/devices |
获取设备信息
使用hexdump
命令hexdump /dev/input/event1
来获取设备信息
1 | //下表中与值相对应的事件类型type |
下表为使用一个手指点击触摸屏返回的数据
序号 | 秒 | 微秒 | type | code | value | type code value数值代表的详细信息 |
---|---|---|---|---|---|---|
0000000 | 7180 0000 | 57bc 0006 | 0003 | 0039 | 0014 0000 | EV_ABS ABS_MT_TRACKING_ID 14 |
0000010 | 7180 0000 | 57bc 0006 | 0003 | 0035 | 006f 0000 | EV_ABS ABS_MT_POSITION_X |
0000020 | 7180 0000 | 57bc 0006 | 0003 | 0036 | 013a 0000 | EV_ABS ABS_MT_POSITION_Y |
0000030 | 7180 0000 | 57bc 0006 | 0003 | 0030 | 001e 0000 | EV_ABS ABS_MT_TOUCH_MAJOR |
0000040 | 7180 0000 | 57bc 0006 | 0003 | 003a | 001e 0000 | EV_ABS ABS_MT_PRESSURE |
0000050 | 7180 0000 | 57bc 0006 | 0001 | 014a | 0001 0000 | EV_KEY BTN_TOUCH |
0000060 | 7180 0000 | 57bc 0006 | 0000 | 0000 | 0000 0000 | EV_SYN |
0000070 | 7180 0000 | 27c5 0007 | 0003 | 0039 | ffff ffff | ABS_MT_TRACKING_ID -1 |
0000080 | 7180 0000 | 27c5 0007 | 0001 | 014a | 0000 0000 | EV_KEY BTN_TOUCH |
0000090 | 7180 0000 | 27c5 0007 | 0000 | 0000 | 0000 0000 | EV_SYN |
对于多点触摸会上传ABS_MT_SLOT,数据更加复杂一些,但是原理是差不多的,会看信息表即可
获取设备信息
1 | int ioctl(int fd, unsigned long request, ...) |
_IOC_NR _IOC_TYPE _IOC_SIZE _IOC_DIR可以参考这篇博客
requret可以取以下值:
1 | //include\uapi\linux\input.h |
软件访问硬件的几种方式
1.查询方式
示例:软件调用open函数,传入非阻塞参数O_NONBLOCK,如果驱动程序中有数据则立刻返回数据,否则返回错误
2.休眠唤醒方式
示例:软件调用open函数,不传入非阻塞参数O_NONBLOCK,如果驱动程序中有数据,则返回数据;否则驱动程序休眠,直到有数据唤醒。
3.poll/select方式
在poll/select中传入超时参数,比如ret = poll(fds, nfds, 5000);,则在有数据可读或者有空间可写时,返回数据;否则等到超时时间5s后,返回错误。
poll/select可以检测多个文件,多个事件;
poll/select的缺点:
4.异步通知方式
暂时没写
多线程编程
基本概念
使用top
命令查看系统占用的资源,查看线程数,查看进程数。
线程:thread
进程:process
调度是以线程为单位的,即系统调度的最小单位是线程。进程是线程的集合,资源分配以进程为基本单位。
互斥量:mutex
共享资源:在进程中出现的全局变量,线程都可以访问
唯一标识:进程号:PID 线程号:TID,pthread_t类型
编译时候需要指明库 gcc xxx.c -lpthread
常用函数
查看线程id:
pthread_t pthread_self(void);
返回值为pthread_t,一个长整型创建线程:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
重要参数解释:pthread_t *thread
:如果成功创建线程,则返回线程标识符,保存在thread中const pthread_attr_t *attr
:线程属性(栈大小,优先级等),默认为NULLvoid *(*start_routine)(void *)
:c函数指针,声明了一个名为start_routine的函数指针,该函数指针具有以下特性:函数接收一个参数,且参数为
void *
函数返回一个
void *
指针函数名为
start_routine,start_routine
可以被替换为任意字符void *arg
:传递给start_routine
的参数,可以设置为NULL
。一般情况下,需要指向arg
指向一个全局/堆变量返回值:成功返回0,失败返回错误码
线程退出
- start_routine函数执行return
- 线程调用prhread_exit(void retval)函数
pthread_exit 函数为线程退出函数,在退出时候可以传递一个 void类型的数据带给主线程,若选择不传出数据,可将参数填充为 NULL。函数原型为void pthread_exit(void *retval);
- 调用pthread_cancel(pthread_t thread)函数
函数原型为:int pthread_cancel(pthread_t thread);
传入一个tid,则会强制退出该tid对应的线程。成功返回0 - 需要注意:如果进程中任意线程调用exit(),_exit(),_Exit()函数,则进程退出,所有线程退出。
回收线程
- int pthread_join(pthread_t thread, void **retval);
阻塞状态,直到成功回收线程后才返回,第一个参数为要回收线程的 tid 号,第二个参数为线程回收后接受线程传
出的数据。 - int pthread_tryjoin_np(pthread_t thread, void **retval);
非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回 0,其余参数与 pthread_join 一致
- int pthread_join(pthread_t thread, void **retval);
线程控制-互斥锁
互斥锁
解决临界资源访问冲突问题。通过对临界资源加锁来保护资源只被单个线程操作,待操作结束后解锁,其余线程才可获得操作权。
例如:对某个全局变量进行访问时,通过互斥锁保证同一时间只能被一个线程访问
互斥锁实现的过程可以如下:创建互斥锁
函数原型为:int pthread_mutex_init(phtread_mutex_t *mutex,const pthread_mutexattr_t *restrict attr);
其中:mutex (pthread_mutex_t *)
: 互斥量指针。指向未初始化的互斥锁对象的指针。成功初始化后,这个互斥锁可用于线程间的同步。 参数:attr (const pthread_mutexattr_t *restrict)
: 指向互斥锁属性对象的指针,该对象指定了互斥锁的特性。如果设为NULL
,则使用默认属性。restrict
关键字表明在函数执行期间,attr
指针不与其他访问的内存别名,这允许编译器进行潜在的优化。
返回值 0: 成功,表示互斥锁已成功初始化。errno (错误码): 失败时,返回一个错误编号以指示遇到的错误类型(例如,如果内存不足无法初始化互斥锁,则返回ENOMEM)。函数应在互斥锁首次使用前被调用.一旦初始化,线程可以使用如pthread_mutex_lock
、pthread_mutex_trylock
和pthread_mutex_unlock
等函数来管理对共享资源的访问加锁/去锁(阻塞)
当某一个线程获得了执行权后,执行 lock 函数,一旦加锁成功后,其余线程遇到 lock 函数时候会发生阻塞,直至获取资源的线程执行 unlock 函数后。unlock 函数会唤醒其他正在等待互斥量的线程。
加锁函数原型为:int pthread_mutex_lock(pthread_mutex_t *mutex);
成功后会返回 0
去锁函数原型为:int pthread_mutex_unlock(pthread_mutex_t *mutex);
特别注意的是,当获取lock
之后,必须在逻辑处理结束后执行unlock
,否则会发生死锁现象!导致其余线程一直处于阻塞状态,无法执行下去。在使用互斥量的时候,尤其要注意使用pthread_cancel
函数,防止发生死锁现象!加锁(非阻塞模式)
函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
该函数是非阻塞模式通过返回值来判断是否加锁成功,用法与上述阻塞加锁函数一致销毁互斥量(非阻塞)
函数原型:int pthread_mutex_destory(pthread_mutex_t *mutex);
,传入互斥量的指针,就可以完成互斥量的销毁,成功返回 0。
线程控制-信号量
互斥量:防止多个线程同时访问同一个临界资源冲突问题
信号量:通知- 创建信号量
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem_t *sem
:指向信号量结构体的指针,这个结构体需要在调用sem_init
前由用户分配空间。函数会初始化这个信号量。
- 创建信号量
int pshared
:指定信号量是否可以在进程间共享。- 如果
pshared
为0,表示信号量仅在当前进程中有效,不能被其他进程访问。 - 如果
pshared
为非0值(通常为1),则信号量可以在多个进程间共享,要求sem位于共享内存中。
- 如果
unsigned int value
:初始化信号量的值,即信号量计数器的初始值。表示最初可以同时访问共享资源的线程或进程的数量。
- 信号量操作
1
2
3int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
int sem_trywait(sem_t *sem);
sem_wait
:函数作用为检测指定信号量是否有资源可用,若无资源可用会阻塞等待,若有资源可用会自动的执行“sem-1”的操作。所谓的“sem-1”是与上述初始化函数中第三个参数值一致,成功执行会返回 0。sem_post
: 函数会释放指定信号量的资源,执行“sem+1”操作。sem_trywait
:非阻塞方式
- 信号量销毁
int sem_destory(sem_t *sem);
Linux串口
数据成员
ermios 函数族提供了一个常规的终端接口,用于控制非同步通信端口。 这个结构包含了至少下列成员:
1 | struct termios |
常用函数
tc:terminao control cd control flag
函数名 | 描述 |
---|---|
int tcgetattr(int fd, struct termios *termios_p); | 获取终端属性 |
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); | 设置终端属性 |
int tcflush(int fd, int queue_selector); | 清除终端缓冲区 |
int tcflow(int fd, int action); | 控制终端的输入输出流 |
int tcdrain(int fd); | 等待输出缓冲区中的所有字符被发送 |
int tcsendbreak(int fd, int duration); | 发送一个持续duration的BREAK信号 |
int cfsetospeed(struct termios *termios_p, speed_t speed); | 设置输出波特率 |
int cfsetispeed(struct termios *termios_p, speed_t speed); | 设置输入波特率 |
speed_t cfgetospeed(const struct termios *termios_p); | 获取输出波特率 |
speed_t cfgetispeed(const struct termios *termios_p); | 获取输入波特率 |
int cfsetspeed(struct termios *termios_p, speed_t speed); | 设置波特率 |
驱动
准备工作
编译驱动程序之前需要先编译内核,内核的配置文件在arch/arm/configs/
目录下,内核编译过程为:
1
2
3
4
make mrproper
make 100_ask_imx6ull_defconfig
make zImage -j4
make dtbs
生成arch/arm/boot/zImage
和arch/arm/boot/dts/100ask_imx6ull-14x14.dtb
文件,将这两个文件复制到/home/book/nfs_rootfs目录下备用
1
2
cp arch/arm/boot/zImage ~/nfs_rootfs
cp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs
进入内核源码目录,编译内核模块
1
2
cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88/
make modules
安装内核模块到/home/book/nfs_rootfs
1
make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install
安装内核和模块到开发板上,先执行挂在命令,再执行以下命令
1
2
3
4
cp /mnt/zImage /boot
cp /mnt/100ask_imx6ull-14x14.dtb /boot
cp /mnt/lib/modules /lib -rfd
sync
完成以上配置
1 | make mrproper |
1 | cp arch/arm/boot/zImage ~/nfs_rootfs |
1 | cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88/ |
1 | make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install |
1 | cp /mnt/zImage /boot |
驱动程序流程:
- 确定主设备号,也可以让内核分配
- 定义自己的file_operations结构体
- 实现对应的drv_open/drv_read/drv_wtire等函数,填入file_operations结构体
- 把file_operations结构体注册到内核中:register_chrdev(主设备号,设备名,file_operations结构体指针)?
- 入口函数:安装驱动程序时,就会调用此入口函数
- 出口函数:卸载驱动程序,出口函数调用unregister_chrdev(主设备号,设备名)?
- 提供设备信息,自动创建设备节点:class_create,device_create
重要的类class
解释
参考博客:Linux内核中的 struct class 简介
代码中出现的 class 指的是 设备类(device classes),是对于设备的高级抽象。但 实际上 class 也是一个结构体,只不过 class 结构体在声明时是按照类的思想来组织其成员的。
运用 class,可以让用户空间的程序根据自己要处理的事情来调用设备,而不是根据设备被接入到系统的方式或设备的工作原理来调用。
1 | //include\linux\device.h |
重要的类file_operations结构体,参考博客:Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析
1 | //include\linux\fs.h |
常用函数
函数名 | 描述 |
---|---|
unsigned long copy_to_user(void *to, const void *from, unsigned long n); | 这个函数的作用是将内核空间的数据复制到用户空间 to:目标地址(用户空间) from:源地址(内核空间) n:将要拷贝数据的字节数 返回:成功返回0,失败返回没有拷贝成功的数据字节数 |
unsigned long copy_from_user(void *to, const void *from, unsigned long n); | 这个函数的作用是将用户空间的数据复制到内核空间 to:目标地址(内核空间)from:源地址(用户空间)n:将要拷贝数据的字节数返回:成功返回0,失败返回没有拷贝成功的数据字节数 |
led驱动
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)