🐧🐧🐧🐧🐧🐧🐧🐧🐧🐧🐧

一些缩写

.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通常用于处理电子设备中的信号输入和输出。它可以将多个信号源(如不同的传感器或数据源)与多个目标(如不同的处理器或输出设备)进行连接和分配,从而实现信号的复用和转换。
关键词解释:

  1. Input/Output(输入/输出):指设备或系统接收和发送数据的接口。
  2. Multiplexer(多路复用器):一种设备或技术,可以将多个信号或数据流合并为一个信号流进行传输或处理。
  3. Unifier(统一器):用于将不同来源或格式的数据、信号等统一处理或输出的设备或技术。
    IOMUX在许多电子设备和系统中都有应用,如嵌入式系统、微控制器、FPGA等,它可以帮助提高系统的效率和性能,同时减少硬件的复杂性和成本。

一些C语法

c语言中将int变量转换为void *指针或int *指针

在C语言中,将一个整型变量强制转换为void *指针类型通常是为了将整数值作为一个通用指针传递给函数,或者在特定的上下文中使用这个整数的值作为内存地址。进行这样的转换可以用类型转换运算符 (void *)。以下是一个简单的示例:

1
2
int integerValue = 12345;
void *pointerValue = (void *)(intptr_t)integerValue;

在这个例子中,整型变量integerValue的值被转换为一个intptr_t类型,然后再转换为void *类型。intptr_t是一种整数类型,它的大小足够存放指针,这保证了转换过程中不会丢失信息。这种转换方法是可移植性较好的,因为它考虑到了不同平台上指针和整数大小可能不一致的情况。

需要注意的是,这样的转换并不意味着整数真的变成了一个有效的内存地址。如果你打算通过这样的指针去访问内存,必须非常小心,确保这个整数值确实对应于一个合法且可访问的内存地址,否则会导致未定义行为,比如程序崩溃或者数据损坏。在多线程和异步编程中,将整数简单地作为数据通过指针传递给线程函数是一种常见且安全的用途。
于是想到是否解引用pointerValue的值,发现是不可以的。目前暂时不知道将int 变量转换为void指针的目的是什么

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int main()
{
int integerValue = 12345;
int * pointerValue = (int *)integerValue;
//int * pointerValue = (int *)(long)integerValue;
printf("* pointerValue2 = %d",*pointerValue);
return 0;
}

这一部分问题在于:

将一个整数直接转换为指针并解引用该指针,这通常会导致未定义行为(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
2
3
4
5
6
7
02	#include <stdio.h>
03
04 int main()
05 {
06 func_b();
07 return 0;
08}

文件b.c

1
2
3
4
5
6
2	#include <stdio.h>
3
4 void func_b()
5 {
6 printf("This is B\n");
7 }

编译:

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.oyyy.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
2
gcc -o a.o a.c
gcc -o b.o b.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
2
目标 : 依赖1 依赖2 ...
[TAB]命令

当“依赖”比“目标”新,执行它们下面的命令。我们要把上面三个命令写成makefile规则,如下:

1
2
3
4
5
6
7
test :a.o b.o  //test是目标,它依赖于a.o b.o文件,一旦a.o或者b.o比test新的时候,
就需要执行下面的命令,重新生成test可执行程序。
gcc -o test a.o b.o
a.o : a.c //a.o依赖于a.c,当a.c更加新的话,执行下面的命令来生成a.o
gcc -c -o a.o a.c
b.o : b.c //b.o依赖于b.c,当b.c更加新的话,执行下面的命令,来生成b.o
gcc -c -o b.o b.c

我们来作一下实验:

在改目录下我们写一个Makefile文件:

文件:Makefile

1
2
3
4
5
6
7
8
1	test:a.o b.o
2 gcc -o test a.o b.o
3
4 a.o : a.c
5 gcc -c -o a.o a.c
6
7 b.o : b.c
8 gcc -c -o b.o b.c

上面是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
2
gcc -c -o a.o a.c
gcc -o test a.o b.o

我们第一次执行make的时候,会执行下面三条命令(三条命令都执行):

1
2
3
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o

再次执行make 就会显示下面的提示:

1
make: `test' is up to date.

我们再次执行make就会判断Makefile文件中的依赖,发现依赖没有更新,所以目标文件就不会重现生成,就会有上面的提示。当我们修改a.c后,重新执行make,

就会执行下面两条指令:

1
2
gcc -c -o a.o a.c
gcc -o test a.o b.o

我们同时修改a.c b.c,执行make就会执行下面三条指令。

1
2
3
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o

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
2
3
4
5
INCDIR    := $(shell pwd)       //将shell pwd作为变量,通过 $(shell pwd)形式引用这个变量
// C预处理器的flag,flag就是编译器可选的选项
CPPFLAGS    := -nostdlib -nostdinc -I$(INCDIR)/include //-nostdlib表示不使用标准库
//-nostdinc表示不使用标准头文件
//-I是用来指定相对路径的,这里表示我们需要预处理的文件所在的路径是相对路径下的include文件夹

文件IO

标准io和系统io的区别在于标准io引入了一个用户buffer,系统io没有。
alt text

系统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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//D:\Linux-4.9.88\include\linux\input.h
struct input_dev {
const char *name;
const char *phys;
const char *uniq;
struct input_id id;

unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

unsigned long evbit[BITS_TO_LONGS(EV_CNT)];
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)];
unsigned long relbit[BITS_TO_LONGS(REL_CNT)];
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];

unsigned int hint_events_per_packet;

unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;

int (*setkeycode)(struct input_dev *dev,
const struct input_keymap_entry *ke,
unsigned int *old_keycode);
int (*getkeycode)(struct input_dev *dev,
struct input_keymap_entry *ke);

struct ff_device *ff;

unsigned int repeat_key;
struct timer_list timer;

int rep[REP_CNT];

struct input_mt *mt;

struct input_absinfo *absinfo;

unsigned long key[BITS_TO_LONGS(KEY_CNT)];
unsigned long led[BITS_TO_LONGS(LED_CNT)];
unsigned long snd[BITS_TO_LONGS(SND_CNT)];
unsigned long sw[BITS_TO_LONGS(SW_CNT)];

int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value);

struct input_handle __rcu *grab;

spinlock_t event_lock;
struct mutex mutex;

unsigned int users;
bool going_away;

struct device dev;

struct list_head h_list;
struct list_head node;

unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;

bool devres_managed;
};
设备ID结构体为
```C
struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};
__u16 bustype;//枚举类型,如usb,pci等等
取值为:
#define BUS_PCI 0x01
#define BUS_ISAPNP 0x02
#define BUS_USB 0x03
#define BUS_HIL 0x04
#define BUS_BLUETOOTH 0x05
#define BUS_VIRTUAL 0x06

#define BUS_ISA 0x10
#define BUS_I8042 0x11
#define BUS_XTKBD 0x12
#define BUS_RS232 0x13
#define BUS_GAMEPORT 0x14
#define BUS_PARPORT 0x15
#define BUS_AMIGA 0x16
#define BUS_ADB 0x17
#define BUS_I2C 0x18
#define BUS_HOST 0x19
#define BUS_GSC 0x1A
#define BUS_ATARI 0x1B
#define BUS_SPI 0x1C
#define BUS_RMI 0x1D
#define BUS_CEC 0x1E
#define BUS_INTEL_ISHTP 0x1F
__u16 vendor;__u16 product;__u16 version;厂商号,产品号,版本号

内核使用input_event结构体来描述“输入事件”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//include\uapi\linux\input.h
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
struct timeval
{
long tv_sec;
long tv_usec;
};
struct timeval time;记录了input_event时间发生的时间,这个包括秒和微秒,时间起点是系统启动的时刻。
__u16 type;记录了哪一类事件
在include\uapi\linux\input-event-codes.h中,详细定义了事件类型
/*
* Event types
*/

#define EV_SYN 0x00//表示设备支持所有的事件?
#define EV_KEY 0x01//键盘或者按键
#define EV_REL 0x02//relative,鼠标或者相对坐标位置
#define EV_ABS 0x03//absolute,手写板的值,绝对整数值
#define EV_MSC 0x04//其他类型事件
#define EV_SW 0x05
#define EV_LED 0x11//LED设备事件
#define EV_SND 0x12//蜂鸣器事件
#define EV_REP 0x14//重复按键类型事件
#define EV_FF 0x15
#define EV_PWR 0x16//电源管理事件
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)

__u16 code;记录了哪一个事件
code 根据type的不同而有不同的含义
例如:type = EV_KEY//按键事件,code表示键盘code或鼠标button值
#define REL_X 0x00
#define REL_Y 0x01
#define REL_Z 0x02
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define ...
#define KEY_MAX 0x2ff
#define KEY_CNT (KEY_MAX+1)
例如:
type = EV_REL时,code表示操作的是哪个坐标轴,如REL_X,REL_Y
/*
* Relative axes
*/

#define REL_X 0x00
#define REL_Y 0x01
#define REL_Z 0x02
#define REL_RX 0x03
#define REL_RY 0x04
#define REL_RZ 0x05
#define REL_HWHEEL 0x06
#define REL_DIAL 0x07
#define REL_WHEEL 0x08
#define REL_MISC 0x09
#define REL_MAX 0x0f
#define REL_CNT (REL_MAX+1)

例如:type = EV_ABS时,code表示绝对坐标轴的值
/*
* Absolute axes
*/

#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#define ABS_RX 0x03
#define ABS_RY 0x04
#define ABS_RZ 0x05
#define ...

//表示正在修改的多点触控槽位,用于跟踪多个接触点。
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
//触摸椭圆的主要轴长,表示接触区域的最大尺寸。
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
//触摸椭圆的次要轴长,如果接触区域是圆形,则忽略此值
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
//接近椭圆的主要轴长,可能比实际接触区域更大。
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
//接近椭圆的次要轴长,如果接近区域是圆形,则忽略此值
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
//椭圆的方向,用于描述接触点的倾斜角度。
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
// 触摸中心的X坐标位置。
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
//触摸设备的类型,如手指、笔等。
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
//将一组数据包分组为一个“blob”(数据块),用于关联相关的触摸事件。
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
//跟踪接触的唯一ID,用于识别特定的触摸接触。或许应该翻译成初始触摸点唯一id?
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
//在接触区域上的压力大小。
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
//接触点悬停时的距离,用于检测未实际接触但靠近传感器的情况。
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
//工具中心的X坐标位置,可能与ABS_MT_POSITION_X不同,例如对于笔或其他工具。
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */


#define ABS_MAX 0x3f
#define ABS_CNT (ABS_MAX+1)
__s32 value;记录了事件的值
同样的value也会根据type的值不同而有不同的含义
例如:type = EV_KEY时,value表示按键的值,0表示按键没有按下,1表示按键按下//这里没有找到定义,网上查的
type = EV_REL时,value表示移动的值和方向(正负)
type = EV_ABS时,value表示移动的绝对坐标

当一个事件上传完之后,会上报一个“同步事件”,表示数据上传完毕。同步事件也是一个input_event结构体,type = EV_SYN,code = SYN_REPORT,value = 0.即0 0 0

查看有哪些输入设备:

1
2
3
4
5
6
7
8
9
10
11
ls /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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
cat /proc/bus/input/devices

I: Bus=0019 Vendor=0000 Product=0000 Version=0000//I 设备ID
N: Name="20cc000.snvs:snvs-powerkey"//Name
P: Phys=snvs-pwrkey/input0//物理路径
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0//位于 sys 文件系统的路径
U: Uniq=//唯一标识符
H: Handlers=kbd event0 evbug//输入句柄Handler
B: PROP=0//B位图
B: EV=3
B: KEY=100000 0 0 0
//与04_嵌入式Linux应用开发基础知识\source\11_input\01_app_demo\01_get_input_info.c结果相同
I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"//触摸屏即#define BUS_I2C 0x18
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: PROP=2
B: EV=b
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0
B: ABS=6e18000 0

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c

获取设备信息

使用hexdump命令hexdump /dev/input/event1来获取设备信息

1
2
3
4
5
6
7
//下表中与值相对应的事件类型type
#define EV_ABS 0x03//绝对坐标事件
#define EV_KEY 0x01//键盘或者按键事件
#define EV_SYN 0x00
//下表中与值相对应的事件code
#define BTN_TOUCH 0x14a

下表为使用一个手指点击触摸屏返回的数据

序号 微秒 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
2
3
4
5
6
7
8
9
10
11
12
int ioctl(int fd, unsigned long request, ...)
//输入格式request要求:
//include\uapi\asm-generic\ioctl.h
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
dir:_IOC_READ 读数据 _IOC_WRITE 写数据
size:ioctl传输的字节数
type:
nr:

_IOC_NR _IOC_TYPE _IOC_SIZE _IOC_DIR可以参考这篇博客

requret可以取以下值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//include\uapi\linux\input.h
#define EVIOCGVERSION _IOR('E', 0x01, int) /* get driver version */
#define EVIOCGID _IOR('E', 0x02, struct input_id) /* get device ID */
#define EVIOCGREP _IOR('E', 0x03, unsigned int[2]) /* get repeat settings */
#define EVIOCSREP _IOW('E', 0x03, unsigned int[2]) /* set repeat settings */

#define EVIOCGKEYCODE _IOR('E', 0x04, unsigned int[2]) /* get keycode */
#define EVIOCGKEYCODE_V2 _IOR('E', 0x04, struct input_keymap_entry)
#define EVIOCSKEYCODE _IOW('E', 0x04, unsigned int[2]) /* set keycode */
#define EVIOCSKEYCODE_V2 _IOW('E', 0x04, struct input_keymap_entry)

#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */
#define EVIOCGPHYS(len) _IOC(_IOC_READ, 'E', 0x07, len) /* get physical location */
#define EVIOCGUNIQ(len) _IOC(_IOC_READ, 'E', 0x08, len) /* get unique identifier */
#define EVIOCGPROP(len) _IOC(_IOC_READ, 'E', 0x09, len) /* get device properties */


#define EVIOCGBIT(ev,len) _IOC(_IOC_READ, 'E', 0x20 + (ev), len) /* get event bits */
#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* get abs value/limits */
#define EVIOCSABS(abs) _IOW('E', 0xc0 + (abs), struct input_absinfo) /* set abs value/limits */
#define ...

软件访问硬件的几种方式

1.查询方式

示例:软件调用open函数,传入非阻塞参数O_NONBLOCK,如果驱动程序中有数据则立刻返回数据,否则返回错误

2.休眠唤醒方式

示例:软件调用open函数,不传入非阻塞参数O_NONBLOCK,如果驱动程序中有数据,则返回数据;否则驱动程序休眠,直到有数据唤醒

3.poll/select方式

在poll/select中传入超时参数,比如ret = poll(fds, nfds, 5000);,则在有数据可读或者有空间可写时,返回数据;否则等到超时时间5s后,返回错误。
poll/select可以检测多个文件,多个事件;
poll/select的缺点:
pic/poll检测事件

4.异步通知方式

暂时没写

多线程编程

基本概念

使用top命令查看系统占用的资源,查看线程数,查看进程数。
线程:thread
进程:process
调度是以线程为单位的,即系统调度的最小单位是线程。进程是线程的集合,资源分配以进程为基本单位。

互斥量:mutex
共享资源:在进程中出现的全局变量,线程都可以访问
唯一标识:进程号:PID 线程号:TID,pthread_t类型
编译时候需要指明库 gcc xxx.c -lpthread

常用函数

  1. 查看线程id:pthread_t pthread_self(void);返回值为pthread_t,一个长整型

  2. 创建线程:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
    重要参数解释:

    1. pthread_t *thread:如果成功创建线程,则返回线程标识符,保存在thread中

    2. const pthread_attr_t *attr:线程属性(栈大小,优先级等),默认为NULL

    3. void *(*start_routine)(void *):c函数指针,声明了一个名为start_routine的函数指针,该函数指针具有以下特性:

    4. 函数接收一个参数,且参数为void *

    5. 函数返回一个void *指针

    6. 函数名为start_routine,start_routine可以被替换为任意字符

    7. void *arg:传递给start_routine的参数,可以设置为NULL。一般情况下,需要指向arg指向一个全局/堆变量

    8. 返回值:成功返回0,失败返回错误码

  3. 线程退出

    1. start_routine函数执行return
    2. 线程调用prhread_exit(void retval)函数
      pthread_exit 函数为线程退出函数,在退出时候可以传递一个 void
      类型的数据带给主线程,若选择不传出数据,可将参数填充为 NULL。函数原型为void pthread_exit(void *retval);
    3. 调用pthread_cancel(pthread_t thread)函数
      函数原型为:int pthread_cancel(pthread_t thread);传入一个tid,则会强制退出该tid对应的线程。成功返回0
    4. 需要注意:如果进程中任意线程调用exit(),_exit(),_Exit()函数,则进程退出,所有线程退出。
  4. 回收线程

    1. int pthread_join(pthread_t thread, void **retval);
      阻塞状态,直到成功回收线程后才返回,第一个参数为要回收线程的 tid 号,第二个参数为线程回收后接受线程传
      出的数据。
    2. int pthread_tryjoin_np(pthread_t thread, void **retval);
      非阻塞模式回收函数,通过返回值判断是否回收掉线程,成功回收则返回 0,其余参数与 pthread_join 一致
  5. 线程控制-互斥锁

    1. 互斥锁
      解决临界资源访问冲突问题。通过对临界资源加锁来保护资源只被单个线程操作,待操作结束后解锁,其余线程才可获得操作权。
      例如:对某个全局变量进行访问时,通过互斥锁保证同一时间只能被一个线程访问
      互斥锁实现的过程可以如下:

    2. 创建互斥锁
      函数原型为: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_lockpthread_mutex_trylockpthread_mutex_unlock等函数来管理对共享资源的访问

    3. 加锁/去锁(阻塞)
      当某一个线程获得了执行权后,执行 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 函数,防止发生死锁现象!

    4. 加锁(非阻塞模式)
      函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);该函数是非阻塞模式通过返回值来判断是否加锁成功,用法与上述阻塞加锁函数一致

    5. 销毁互斥量(非阻塞)
      函数原型:int pthread_mutex_destory(pthread_mutex_t *mutex);,传入互斥量的指针,就可以完成互斥量的销毁,成功返回 0。

  6. 线程控制-信号量
    互斥量:防止多个线程同时访问同一个临界资源冲突问题
    信号量:通知

    1. 创建信号量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. 信号量操作
    1
    2
    3
    int 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:非阻塞方式
  1. 信号量销毁int sem_destory(sem_t *sem);

Linux串口

数据成员

ermios 函数族提供了一个常规的终端接口,用于控制非同步通信端口。 这个结构包含了至少下列成员:

1
2
3
4
5
6
7
8
9
struct termios
{
unsigned short c_iflag; /* 输入模式标志*/
unsigned short c_oflag; /* 输出模式标志*/
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /*区域模式标志或本地模式标志或局部模式*/
unsigned char c_line; /*行控制line discipline */
unsigned char c_cc[NCC]; /* 控制字符特性*/
};

常用函数

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/zImagearch/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. 确定主设备号,也可以让内核分配
  2. 定义自己的file_operations结构体
  3. 实现对应的drv_open/drv_read/drv_wtire等函数,填入file_operations结构体
  4. 把file_operations结构体注册到内核中:register_chrdev(主设备号,设备名,file_operations结构体指针)?
  5. 入口函数:安装驱动程序时,就会调用此入口函数
  6. 出口函数:卸载驱动程序,出口函数调用unregister_chrdev(主设备号,设备名)?
  7. 提供设备信息,自动创建设备节点:class_create,device_create

重要的类class解释
参考博客:Linux内核中的 struct class 简介
代码中出现的 class 指的是 设备类(device classes),是对于设备的高级抽象。但 实际上 class 也是一个结构体,只不过 class 结构体在声明时是按照类的思想来组织其成员的。
运用 class,可以让用户空间的程序根据自己要处理的事情来调用设备,而不是根据设备被接入到系统的方式或设备的工作原理来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//include\linux\device.h
struct class {
const char *name; // 类名称
struct module *owner; // 类所属的模块,比如 usb模块、led模块等

struct class_attribute *class_attrs; // 类所添加的属性
const struct attribute_group **dev_groups; // 类所包含的设备所添加的属性
struct kobject *dev_kobj; // 用于标识 类所包含的设备属于块设备还是字符设备

int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); // 用于在设备发出 uevent 消息时添加环境变量
char *(*devnode)(struct device *dev, umode_t *mode); // 设备节点的相对路径名

void (*class_release)(struct class *class); // 类被释放时调用的函数
void (*dev_release)(struct device *dev); // 设备被释放时调用的函数

int (*suspend)(struct device *dev, pm_message_t state); // 设备休眠时调用的函数
int (*resume)(struct device *dev); // 设备被唤醒时调用的函数

const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);

const struct dev_pm_ops *pm; // 用于电源管理的函数

struct subsys_private *p; // 指向 class_private 结构的指针
};

重要的类file_operations结构体,参考博客:Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//include\linux\fs.h
struct file_operations {

struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES

loff_t (*llseek) (struct file *, loff_t, int);//用来修改文件当前的读写位置

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中同步读取数据

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据

ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作

ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作

int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL

unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl

long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替


int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间

int (*open) (struct inode *, struct file *); //打开

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *); //关闭

int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据

int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据

int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **);

};

常用函数

函数名 描述
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)