2 编译器的使用
2.1 gcc 编译过程
使用 gcc -o hello hello.c -v
之后,会打印出详细的编译过程,去除不重要的信息后,最后剩余的就是三步,分别是:编译为 .s
汇编文件,汇编为 .o
机器码,链接为最终的可执行文件。具体三条信息如下:
/usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/ccX6eBn7.s
as -v --64 -o /tmp/cclwBtJD.o /tmp/ccX6eBn7.s
/usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper -plugin-opt=-fresolution=/tmp/cc7xr459.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --sysroot=/ --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. /tmp/cclwBtJD.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
进一步去除编译选项后,命令主题如下:
# 编译
/usr/lib/gcc/x86_64-linux-gnu/7/cc1 hello.c -o /tmp/ccX6eBn7.s
# 汇编
as -o /tmp/cclwBtJD.o /tmp/ccX6eBn7.s
# 链接
/usr/lib/gcc/x86_64-linux-gnu/7/collect2 -o hello /tmp/cclwBtJD.o
手动分步编译:
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
gcc -o hello hello.o
2.1.2 编译多个文件
- 一起编译
gcc -o test main.c sub.c
- 分开编译,统一链接
gcc -c -o main.o main.c gcc -c -o sub.o sub.c gcc -o test main.o sub.o
2.1.3 制作、使用动态库
gcc -shared -o libsub.so sub.o sub2.o sub3.o(可以使用多个.o 生成动态库)
gcc -o test main.o -lsub -L /libsub.so/所在目录/
运行
# 放在某个目录比如/a
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
./test
2.1.4 制作、使用静态库
ar crs libsub.a sub.o sub2.o sub3.o(可以使用多个.o 生成静态库)
gcc -o test main.o libsub.a (如果.a 不在当前目录下,需要指定它的绝对或相对路径)
如果执行 arm-buildroot-linux-gnueabihf-gcc -c -o sub.o sub.c 交叉编译需要在最后面加上-fPIC 参数
2.1.5 很有用的选项
gcc -E main.c // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o main.o main.c // 生成依赖文件 abc.dep,后面 Makefile 会用
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
2.4 警告选项(Warning Option)-Wall
开启全部警告
gcc -Wall -c main.c
2.5 调试选项(Debugging Option)-g
除了基本的 -g
以外,还有 -gstabs+
,-gstabs
,-gxcoff+
,-gxcoff
,-gdwarf+
或 -gdwarf
2.6 优化选项(Optimization Option)
O0
: 不优化, 只有声明了 register 的变量才分配使用寄存器。O1
:-fthread-jumps
和-fdefer-pop
选项将被打开。在有 delay slot 的机器上,-fdelayed-branch
选项将被打开。在即使没有帧指针 (frame pointer)也支持调试的机器上,-fomit-frame-pointer
选项将被打开。某些机器上还可能会打开其他选项。O2
: 除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作。例如不进行循环展开(loop unrolling)和函数内嵌(inlining)。O3
: 除了打开-O2 所做的一切,它还打开了-finline-functions选项。
2.7 链接器选项(Linker Option)
-llibrary
,库文件的真正名字是liblibrary.a
, 指定-l
选项和指定文件名的唯一区别是,-l
选项用lib
和.a
把 library 包裹起来,而且搜索一些目录。-nostartfiles
不链接系统标准启动文件,而标准库文件仍然正常使用:-nostdlib
不链接系统标准启动文件和标准库文件,只把指定的文件传递给链接器。这个选项常用于编译内核、bootloader 等程序,它们不需要启动文件、标准库文件。-static
阻止链接共享库-shared
生成一个共享 OBJ 文件,它可以和其他 OBJ 文件链接产生可执行文件。当不想以源代码发布程序时,可以使用-shared 选项生成库文件.-Xlinker option
。如果需要传递携带参数的选项,必须使用两次-Xlinker
,一次传递选项,另一次传递其参数。例如,如果传递-assert definitions
,要成-Xlinker -assert -Xlinker definitions
,
2.8 目录选项(Directory Option)
-Bprefix
这个选项指出在何处寻找可执行文件,库文件,以及编译器自己的数据文件。编译器驱动程序需要使用某些工具,比如:cpp',
cc1' (或 C++的cc1plus'),
as'和`ld'。
第3章 Makefile 的使用
3.1 配套视频内容大纲
Makefile 规则与示例
- 介绍 Makefile 的 2 个函数
$(foreach var,list,text)
: 对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。objs := a.o b.o dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d
$(wildcard pattern)
: pattern 所列出的文件是否存在,把存在的文件都列出来。src_files := $( wildcard *.c) // 最终 src_files 中列出了当前目录下的所有.c 文件
第4章 文件 IO
4.2 怎么访问文件?
4.2.2 不是通用的函数:ioctl/mmap
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能.
buf = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd_old, 0);
if (buf == MAP_FAILED)
{
printf("can not mmap file %s\n", argv[1]);
return -1;
}
if (write(fd_new, buf, stat.st_size) != stat.st_size)
{
printf("can not write %s\n", argv[2]);
return -1;
}
** mmap 详细介绍,可以参考 认真分析mmap:是什么 为什么 怎么用
4.3 怎么知道这些函数的用法?
man 的几种分类
- Executable programs or shell commands // 命令
- System calls (functions provided by the kernel) // 系统调用,比如 man 2 open
- Library calls (functions within program libraries) // 函数库调用
- Special files (usually found in /dev) // 特殊文件, 比如 man 4 tty
- File formats and conventions eg /etc/passwd // 文件格式和约定, 比如 man 5 passwd
- Games // 游戏
- Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) / /杂项
- System administration commands (usually only for root) // 系统管理命令
- Kernel routines [Non standard] // 内核例程
常用快捷键
- f 往前翻一页
- b 往后翻一页
- /patten 往前搜
- ?patten 往后搜
第5章 Framebuffer 应用编程
5.1 LCD 操作原理
- 32BPP,一般只设置其中的低 24 位,高 8 位表示透明度,一般的 LCD 都不支持
- 24BPP,硬件上为了方便处理,在 Framebuffer 中也是用 32 位来表示,效果跟 32BPP 是一样的
- 16BPP,常用的是 RGB565;很少的场合会用到 RGB555,这可以通过 ioctl 读取驱动程序中的 RGB 位偏移来确定使用哪一种格式
5.3 Framebuffer 程序分析
5.3.1 打开设备
fd_fb = open("/dev/fb0", O_RDWR);
if (fd_fb < 0) {
printf("can't open /dev/fb0\n");
return -1;
}
5.3.2 获取 LCD 参数
LCD 驱动程序给 APP 提供 2 类参数:可变的参数 fb_var_screeninfo、固定的参数 fb_fix_screeninfo。编写应用程序时主要关心可变参数,它的结构体定义如下(#include <linux/fb.h>)
static struct fb_var_screeninfo var;
// 获取 fb_var_screeninfo
if (ioctl(fd_fb, FBIOGET_VSCREENINFO, &var)) {
printf("can't get var\n");
return -1;
}
5.3.3 映射 Framebuffer
screen_size = var.xres * var.yres * var.bits_per_pixel / 8;
fb_base = (unsigned char *)mmap(NULL , screen_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_fb, 0);
if (fb_base == (unsigned char *)-1) {
printf("can't mmap\n");
return -1;
}
第6章 文字显示
6.1 字符的编码方式
6.1.2 UNICODE 编码实现
- utf16: 分为 le 和 be 两种,都是占用两个字节
- utf8: 可变字节,ascii 还是一个字节,中文之类的会用三个字节,第一个字节开头
1110
表示当前字符需要占用3个字节,第二个和第三个字节开头都是10
,表示使用了1个字节。 第一个字节的低4位和第二第三字节的低6位,一起组成16位的字符数据。
6.3 中文字符的点阵显示
6.3.1 指定编码格式
-finput-charset
, 编译程序时,指定源文件字符编码参数-finput-charset=GB2312
,-finput-charset=UTF-8
-fexec-charset
, 编译程序时,指定可执行文件编码参数-fexec-charset=GB2312
,-fexec-charset=UTF-8
.
如果 -finput-charset
和 -fexec-charset
不一致,编译器会自动进行格式转换。 一般情况下,指定 -fexec-charset
即可。但是如果你源文件使用了 gb2312 保存,那就需要指定 -finput-charset
6.3.4 汉字点阵显示实验
字库频繁使用,使用 mmap 每次只需要 1次复制即可。
4787 fd_hzk16 = open("HZK16", O_RDONLY);
4788 if (fd_hzk16 < 0)
4789 {
4790 printf("can't open HZK16\n");
4791 return -1;
4792 }
4793 if(fstat(fd_hzk16, &hzk_stat))
4794 {
4795 printf("can't get fstat\n");
4796 return -1;
4797 }
4798 hzkmem = (unsigned char *)mmap(NULL , hzk_stat.st_size, PROT_READ, MAP_SHARE
D, fd_hzk16, 0);
4799 if (hzkmem == (unsigned char *)-1)
4800 {
4801 printf("can't mmap for hzk16\n");
4802 return -1;
4803 }
6.4 交叉编译程序:以 freetype 为例
6.4.3 交叉编译程序的万能命令
./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp
, 这个命令里面的--host
用来指定当前的交叉编译工具链,--prefix
用来指定编译安装目录。 这个命令执行完了之后,就可以使用make
来编译项目了,再使用make install
来安装到指定目录。cp -rfd
, d 选项用于需要复制的文件中有链接文件时来使用,能够继续保证正确的链接。- 查看当前编译工具链用到的目录,可以使用
echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v -
这个命令,注意观察输出中的#include "..." search starts here:
,#include <...> search starts here:
,COMPILER_PATH
,LIBRARY_PATH
等等。 - 运行的时候缺少库文件,可以通过
export LD_LIBRARY_PATH=
增加相关的库文件目录。
6.5 使用 freetype 显示单个文字
6.5.3 在 LCD 上显示一个矢量字体
wchar_t
中保存的是 UNICODE, 比较方便字符显示。 普通的char
中保存的是 gb2312 或者 utf-8.wchar_t *chinese_str = L"中gif"";
可以这样使用wchar_t
wcslen(chinese_str)
可以用来获取 unicode 字符串长度。
第7章 输入系统应用编程
7.2 输入系统框架及调试
7.2.1 框架概述
数据流:
- APP 发起读操作,若无数据则休眠
- 用户操作设备,硬件上产生中断;
- 输入系统驱动层对应的驱动程序处理中断:
- 读取到数据,转换为标准的输入事件,向核心层汇报。
- 所谓输入事件就是一个“struct input_event”结构体。
- 核心层可以决定把输入事件转发给上面哪个 handler 来处理:
- 从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如:evdev_handler、kbd_handler、joydev_handler 等等。
- 最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等,APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。
- 当 APP 正在等待数据时,evdev_handler 会把它唤醒,这样 APP 就可以返 回数据。
- APP 对输入事件的处理:
- APP 获 得 数 据 的 方 法 有 2 种 : 直 接 访 问 设 备 节 点 ( 比 如 /dev/input/event0,1,2,...),或者通过 tslib、libinput 这类库来间接访问设备节点。这些库简化了对数据的处理。
7.2.2 编写 APP 需要掌握的知识
- 内核中怎么表示一个输入设备?
include/linux/input.h
struct input_dev { ... // 支持事件类型, key/rel/abs 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)]; ... }
-
struct input_event
在include/uapi/linux/input.h
中struct input_event { // include/uapi/linux/time.h struct timeval time; // include/uapi/linux/input-event-codes.h , EV_* __u16 type; // include/uapi/linux/input-event-codes.h , KEY_*, REL_*, ABS_* __u16 code; // value 可以是 0(表示按键被按下)、1(表示按键被松开)、2(表示长按); 或者坐标值等等 __s32 value; };
- 数据全部上报后,驱动会再发一个“同步事件”,表示上报结束。同步事件也是一个 input_event 结构体,它的 type、code、value 三项都是 0。
- 查看输入节点
ls -l /dev/input/*
看看有哪些 event - 获取输入节点的详细信息
cat /proc/bus/input/devices
, 可以指导输入设备具体对应 input 下面的 event 序号。 - 节点详细信息中,I 表示
struct input_id
, N 表示 name, P 表示物理路径,S 表示 sys 路径,U unqiue code, H 设备关联的句柄列表,B 表示位图 - B 位图,按照二进制对应的 1 所在的位置,来确定含义。
B: EV=b
,为 1011 , 表示支持 EV_SYN(0)、EV_KEY(1)、EV_ABS(3).B: ABS=2658000 3
, 表示支持 0、1、47、48、50、53、54 功能。 hexdump /dev/input/event1
使用 hexdump 来获取 event1 即触摸屏的输入数据。
7.3 不使用库的应用程序示例
7.3.3 获取设备信息
int ioctl(int fd, unsigned long request, ...);
通过 ioctl 获取设备信息, 有些驱动程序对 request 的格式有要求. 可以用来获取设备版本号,id,支持的类型等等相关信息。// include\uapi\asm-generic\ioctl.h #define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ // bit 29 , _IOC_READ, _IOC_WRITE ((type) << _IOC_TYPESHIFT) | \ // bit 8, 具体的驱动程序决定 ((nr) << _IOC_NRSHIFT) | \ // bit 0, 具体的驱动程序决定 ((size) << _IOC_SIZESHIFT)) // bit 16, ioctl 能传输数据的最大字节数
7.3.4 查询方式
open 时候,用 O_NONBLOCK
,然后用 read 来读就可以。如果驱动程序中有数据,那么 APP 的 read函数会返回数据,否则也会立刻返回错误。
7.3.5 休眠-唤醒方式
open 时候,不用 O_NONBLOCK
,read 没数据,就休眠。
7.3.6 POLL/SELECT 方式
用 poll/select
来监控输入文件,有数据,再用 read 来读取。
struct pollfd fds[1];
int timeout_ms = 5000;
int ret;
fds[0].fd = fd;
fds[0].events = POLLIN;
ret = poll(fds, 1, timeout_ms);
if ((ret == 1) && (fds[0].revents & POLLIN))
{
read(fd, &val, 4);
printf("get button : 0x%x\n", val);
}
7.3.7 异步通知方式
-
信号在
include\uapi\asm-generic\signal.h
中#define SIGHUP 1 #define SIGINT 2 #define SIGQUIT 3 #define SIGILL 4 #define SIGTRAP 5 #define SIGABRT 6 #define SIGIOT 6 #define SIGBUS 7 #define SIGFPE 8 #define SIGKILL 9 ... #define SIGIO 29 #define SIGPOLL SIGIO ...
-
设置信号的几个步骤
// 编写信号处理函数: static void sig_func(int sig) { int val; read(fd, &val, 4); printf("get button : 0x%x\n", val); } // 注册信号处理函数: signal(SIGIO, sig_func); // 打开驱动: fd = open(argv[1], O_RDWR); // 把进程 ID 告诉驱动: fcntl(fd, F_SETOWN, getpid()); // 使能驱动的 FASYNC 功能: flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags | FASYNC);
7.4 电阻屏和电容屏
7.4.1 电阻屏
- 按下
EV_KEY BTN_TOUCH 1 /* 按下 */ EV_ABS ABS_PRESSURE 1 /* 压力值,可以上报,也可以不报,可以是其他压力值 */ EV_ABS ABS_X x_value /* X 坐标 */ EV_ABS ABS_Y y_value /* Y 坐标 */ EV_SYNC 0 0 /* 同步事件 */
- 松开
EV_KEY BTN_TOUCH 0 /* 松开 */ EV_ABS ABS_PRESSURE 0 /* 压力值,可以上报,也可以不报 */ EV_SYNC 0 0 /* 同步事件 */
7.4.2 电容屏
- 官方文档,Linux 内 核 Documentation\input\multi-touch-protocol.rst
- 多点触摸数据
EV_ABS ABS_MT_SLOT 0 // 这表示“我要上报一个触点信息了”,用来分隔触点信息 EV_ABS ABS_MT_TRACKING_ID 45 // 这个触点的 ID 是 45 EV_ABS ABS_MT_POSITION_X x[0] // 触点 X 坐标 EV_ABS ABS_MT_POSITION_Y y[0] // 触点 Y 坐标 EV_ABS ABS_MT_SLOT 1 // 这表示“我要上报一个触点信息了”,用来分隔触点信息 EV_ABS ABS_MT_TRACKING_ID 46 // 这个触点的 ID 是 46 EV_ABS ABS_MT_POSITION_X x[1] // 触点 X 坐标 EV_ABS ABS_MT_POSITION_Y y[1] // 触点 Y 坐标 EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
- 触点移动
EV_ABS ABS_MT_SLOT 0 // 这表示“我要上报一个触点信息了”,之前上报过 ID,就不用再上报 ID 了 EV_ABS ABS_MT_POSITION_X x[0] // 触点 X 坐标 EV_SYNC SYN_REPORT 0 // 全部数据上报完毕
- 触点松开
EV_ABS ABS_MT_SLOT 1 // 这表示“我要上报一个触点信息了”,在前面设置过 slot 1 的 ID 为 46 EV_ABS ABS_MT_TRACKING_ID -1 // ID 为-1,表示 slot 1 被松开,即 ID 为 46 的触点被松开 EV_SYNC SYN_REPORT // 全部数据上报完毕
- 实际数据单点,可以看到不仅仅按照电容屏的格式上报数据,同时也按照电阻屏的数据上报了。
- 实际数据多点, 为了兼容老程序,它也上报了 ABS_X、ABS_Y 数据,但是只上报第 1 个触点 的数据。
7.5 tslib
使用 tslib ,就不需要自己使用 open 之类的函数,并且可以对原始数据进行更多的处理,比如过滤跳动过大的数据
7.5.1 tslib 框架分析
- 文件组织, 其中含有
mt
的表示多点触摸 - 代码框架
tslib,暂时不太需要,以后需要的时候,再回头看。
第8章 网络通信
8.1 网络通信概述
8.1.3 两种传输方式:TCP/UDP
- tcp
- udp
8.2 网络编程主要函数介绍
8.2.1 socket 函数
int socket(int domain, int type,int protocol);
- domain (AF_UNIX 和 AF_INET 等)
- type (SOCK_STREAM,SOCK_DGRAM 等)。
- protocol,一般只要用 0 来代替就可以了。
8.2.2 bind 函数
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
// 由 于系 统 的 兼 容 性 , 我 们 一 般 使 用 另 外 一 个 结 构 (struct sockaddr_in) 来代替。
struct sockaddr_in{
unsigned short sin_family; // 一般为 AF_INET
unsigned short sin_port;
struct in_addr sin_addr; // INADDR_ANY 表示可以和任何的主机通信。
unsigned char sin_zero[8];
}
8.2.3 listen 函数
int listen(int sockfd,int backlog);
- backlog 设置请求排队的最大长度。当有多个客户端程序和服务端相连时,使用这个表示可以介绍的排队长度。
8.2.4 accept 函数
int accept(int sockfd, struct sockaddr *addr,int *addrlen);
- addr,addrlen 是用来给客户端的程序填写的,服务器端只要传递指针就可以了, bind,listen 和 accept 是服务器端用的函数。
8.2.5 connect 函数
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen);
- serv_addr 储存了服务器端的连接信息,其中 sin_add 是服务端的地址。
- addrlen 是 serv_addr 的长度
8.2.6 send 函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- flags 一般置 0。
8.2.7 recv 函数
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- flags 一般置 0。
8.2.8 recvfrom 函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
8.2.9 sendto 函数
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
第9章 多线程编程
9.1 线程的使用
9.1.3 线程的标识 pthread_t
pthread_t pthread_self(void);
, 线程号,其仅仅在其所属的进程上下文中才有意义- 因采用 POSIX 线程接口,故在要编译的时候包含 pthread 库
-lpthread
9.1.4 线程的创建
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
- pthread_t 指针,用来保存新建线程的线程号
- 线程的属性,一般传入 NULL 表示默认属性;
- 函数指针,就是线程执行的函数。这个函数返回值为 void, 形参为 void
- 第四个参数则表示为向线程处理函数传入的参数,若不传入,可用 NULL 填充
9.1.6 线程的退出与回收
- 线程主动退出,
void pthread_exit(void *retval);
- 线程被动退出,
int pthread_cancel(pthread_t thread);
- 线程资源回收(阻塞方式),
int pthread_join(pthread_t thread, void **retval);
- 线程资源回收(非阻塞方式),
int pthread_tryjoin_np(pthread_t thread, void **retval);
9.2 线程的控制
9.2.2 互斥锁 API 简述
- 初始化互斥量 ,
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
或者用宏来快速初始化,pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;
- 互斥量加锁/解锁,
int pthread_mutex_lock(pthread_mutex_t *mutex);
,int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 互斥量加锁(非阻塞方式),
int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 销毁互斥量,
int pthread_mutex_destory(pthread_mutex_t *mutex);
9.2.3 多线程编执行顺序控制
引入了信号量的概念,解决线程执行顺序。
9.2.4 信号量 API 简述
- 初始化信号量,
int sem_init(sem_t *sem,int pshared,unsigned int value);
- 信号量 P/V 操作,
int sem_wait(sem_t *sem);
,int sem_post(sem_t *sem);
- 信号量申请(非阻塞方式),
int sem_trywait(sem_t *sem);
- 信号量销毁 ,
int sem_destory(sem_t *sem);
9.2.5 条件变量
参考《Unix_Linux_Windows_OpenMP 多线程编程.pdf》,作者不详。 条件变量时一种同步机制,用来通知其他线程条件满足了。一般是用来通知对方共享数据的状态信息,因此条件变量时结合互斥量来使用的。
- 创建和销毁条件变量,
// 初始化条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);//cond_at tr 通常为 NULL // 销毁条件变量 int pthread_cond_destroy(pthread_cond_t *cond); 这些函数成功时都返回 0
- 等待条件变量 , 要结合互斥量一起使用
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); ... pthread_mutex_lock(&g_tMutex); // 如果条件不满足则,会 unlock g_tMutex // 条件满足后被唤醒,会 lock g_tMutex pthread_cond_wait(&g_tConVar, &g_tMutex); /* 操作临界资源 */ pthread_mutex_unlock(&g_tMutex);
- 通知条件变量
int pthread_cond_signal(pthread_cond_t *cond);
, 只会唤醒一个等待 cond 条件变量的线程
第10章 I2C 应用编程
10.1 I2C 视频介绍
10.1.2 I2C 软件框架
10.3 SMBus 协议
Linux 内核文档:Documentation\i2c\smbus-protocol.rst
10.3.1 SMBus 是 I2C 协议的一个子集
SMBus: System Management Bus,系统管理总线。SMBus 是基于 I2C 协议的,SMBus 要求更严格,SMBus 是 I2C 协议的子集。
10.3.2 SMBus 协议分析
对于 SMBus 协议,它定义了几种数据格式。文档中的 Functionality flag 是 Linux 的某个 I2C 控制器驱动所 支持的功能。比如 Functionality flag: I2C_FUNC_SMBUS_QUICK,表示需要 I2C 控制器支持 SMBus Quick Command
- symbols(符号)
S (1 bit) : Start bit(开始位) Sr (1 bit) : 重复的开始位 P (1 bit) : Stop bit(停止位) R/W# (1 bit) : Read/Write bit. Rd equals 1, Wr equals 0.(读写位) A, N (1 bit) : Accept and reverse accept bit.(回应位) Address(7 bits): I2C 7 bit address. Note that this can be expanded as usual to get a 10 bit I2C address. (地址位,7 位地址) Command Code (8 bits): Command byte, a data byte which often selects a register on the device. (命令字节,一般用来选择芯片内部的寄存器) Data Byte (8 bits): A plain data byte. Sometimes, I write DataLow, DataHigh for 16 bit data. (数据字节,8 位;如果是 16 位数据的话,用 2 个字节来表示:DataLow、DataHigh) Count (8 bits): A data byte containing the length of a block operation. (在 block 操作总,表示数据长度) [..]: Data sent by I2C device, as opposed to data sent by the host adapter. (中括号表示 I2C 设备发送的数据,没有中括号表示 host adapter 发送的数据)
10.3.3 SMBus 和 I2C 的建议
因为很多设备都实现了 SMBus,而不是更宽泛的 I2C 协议,所以优先使用SMBus。
10.4 I2C 系统的重要结构体
Linux 驱动程序, /drivers/i2c
10.4.1 重要结构体
使用一句话概括 I2C 传输:APP 通过 I2C Controller 与 I2C Device 传输数据。
- I2C BUS,
include\linux\i2c.h
,struct i2c_adapter
- I2C BUS 的传输函数,
struct i2c_algorithm
->int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
- 具体的数据消息,
include\uapi\linux\i2c.h
,struct i2c_msg
- I2C Device,
include\linux\i2c.h
,struct i2c_client
->struct i2c_adapter *adapter
,unsigned short addr;
- i2c_msg 中的 flags 用来表示传输方向, 一个 i2c_msg 要么是读,要么是写,所以读操作,正常需要构造两个 i2c_msg
10.4.2 内核里怎么传输数据
- 应用通过 i2c_adapter 与 i2c_client 传输 i2c_msg
- 内核使用 i2c_transfer,
drivers\i2c\i2c-core.c
,int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
10.5 无需编写驱动程序即可访问 I2C 设备
APP 访问硬件肯定是需要驱动程序的,对于 I2C 设备,内核提供了驱动程序drivers/i2c/i2c-dev.c,通过它可以直接使用下面的 I2C 控制器驱动程序来访问 I2C 设备。
10.5.1 体验 I2C-Tools
-
i2cdetect:I2C 检测
// 列出当前的 I2C Adapter(或称为 I2C Bus、I2C Controller) i2cdetect -l // 打印某个 I2C Adapter 的 Functionalities, I2CBUS 为 0、1、2 等整数 i2cdetect -F I2CBUS // 看看有哪些 I2C 设备, I2CBUS 为 0、1、2 等整数 i2cdetect -y -a I2CBUS
-
i2cget:I2C 读
// 读一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 i2cget -f -y I2CBUS CHIP-ADDRESS // 读某个地址上的一个字节: // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus // CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 芯片上寄存器地址 // MODE:有 2 个取值, b-使用`SMBus Read Byte`先发出 DATA-ADDRESS, 再读一个字节, 中间无 P 信号 // c-先 write byte, 在 read byte,中间有 P 信号 i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE // 读某个地址上的 2 个字节: // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus // CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 芯片上寄存器地址 // MODE:w-表示先发出 DATA-ADDRESS,再读 2 个字节 i2cget -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS MODE
-
i2cset:I2C 写
// 写一个字节: I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS 就是要写的数据 i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS // 给 address 写 1 个字节(address, value): // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 8 位芯片寄存器地址; // VALUE: 8 位数值 // MODE: 可以省略,也可以写为 b i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE [b] // 给 address 写 2 个字节(address, value): // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 8 位芯片寄存器地址; // VALUE: 16 位数值 // MODE: w i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE w // SMBus Block Write:给 address 写 N 个字节的数据 // 发送的数据有:address, N, value1, value2, ..., valueN // 跟`I2C Block Write`相比, 需要发送长度 N // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 8 位芯片寄存器地址; // VALUE1~N: N 个 8 位数值 // MODE: s i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN s // I2C Block Write:给 address 写 N 个字节的数据 // 发送的数据有:address, value1, value2, ..., valueN // 跟`SMBus Block Write`相比, 不需要发送长度 N // I2CBUS 为 0、1、2 等整数, 表示 I2C Bus; CHIP-ADDRESS 表示设备地址 // DATA-ADDRESS: 8 位芯片寄存器地址; // VALUE1~N: N 个 8 位数值 // MODE: i i2cset -f -y I2CBUS CHIP-ADDRESS DATA-ADDRESS VALUE1 ... VALUEN i
-
i2ctransfer:I2C 传输(不是基于 SMBus)
// Example (bus 0, read 8 byte at offset 0x64 from EEPROM at 0x50): # i2ctransfer -f -y 0 w1@0x50 0x64 r8 // Example (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50): # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 // Example // first: (bus 0, write 3 byte at offset 0x64 from EEPROM at 0x50) // and then: (bus 0, read 3 byte at offset 0x64 from EEPROM at 0x50) # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3@0x50 # i2ctransfer -f -y 0 w9@0x50 0x64 val1 val2 val3 r3 //如果设备地址不变,后面的设备地址可 省略
-
使用 I2C-Tools 操作传感器 AP3216C
// 使用 SMBus 协议 i2cset -f -y 0 0x1e 0 0x4 i2cset -f -y 0 0x1e 0 0x3 i2cget -f -y 0 0x1e 0xc w i2cget -f -y 0 0x1e 0xe w // 使用 I2C 协议 i2ctransfer -f -y 0 w2@0x1e 0 0x4 i2ctransfer -f -y 0 w2@0x1e 0 0x3 i2ctransfer -f -y 0 w1@0x1e 0xc r2 i2ctransfer -f -y 0 w1@0x1e 0xe r2
10.5.2 I2C-Tools 访问 I2C 设备的 2 种方式
- 指定 I2C 控制器, i2c-dev.c 为每个 I2C 控制器(I2C Bus、I2C Adapter)都生成一个设备节点:/dev/i2c-0、/dev/i2c-1 等等. open 某个/dev/i2c-X 节点,就是去访问该 I2C 控制器下的设备
- 指定 I2C 设备. 通过 ioctl 指定 I2C 设备的地址,
ioctl(file, I2C_SLAVE, address)
,ioctl(file, I2C_SLAVE_FORCE, address)
- 传输数据, I2C 方式:
ioctl(file, I2C_RDWR, &rdwr)
, SMBus 方式:ioctl(file, I2C_SMBUS, &args)
10.5.3 源码流程分析
- 使用 I2C 方式, 示例代码:i2ctransfer.c
- 使用 SMBus 方式, 示例代码:i2cget.c、i2cset.c
10.6 编写 APP 直接访问 EEPROM
Linux 驱动程序: drivers/i2c/i2c-dev.c, 参考 i2C-Tools 的代码
第11章 Linux 串口应用编程
11.1 串口 API
- 需要设置行规程,比如波特率、数据位、停止位、检验位、RAW 模式、一有数据就返回
- 行规程的参数用结构体 termios 来表示,
include\uapi\asm-generic\termios.h
,struct termio
#define NCC 8 struct termio { unsigned short c_iflag; /* input mode flags */ unsigned short c_oflag; /* output mode flags */ unsigned short c_cflag; /* control mode flags */ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ };
- 缩写,tc:terminal contorl , cf: control flag
- 常用函数,
tcgetattr
,tcsetattr
,tcflush
,cfsetispeed
,cfsetospeed
,cfsetspeed
.
11.2 串口收发实验
回环收发