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 规则与示例

  1. 介绍 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 的几种分类

  1. Executable programs or shell commands // 命令
  2. System calls (functions provided by the kernel) // 系统调用,比如 man 2 open
  3. Library calls (functions within program libraries) // 函数库调用
  4. Special files (usually found in /dev) // 特殊文件, 比如 man 4 tty
  5. File formats and conventions eg /etc/passwd // 文件格式和约定, 比如 man 5 passwd
  6. Games // 游戏
  7. Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) / /杂项
  8. System administration commands (usually only for root) // 系统管理命令
  9. 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 框架概述

数据流:

  1. APP 发起读操作,若无数据则休眠
  2. 用户操作设备,硬件上产生中断;
  3. 输入系统驱动层对应的驱动程序处理中断:
    • 读取到数据,转换为标准的输入事件,向核心层汇报。
    • 所谓输入事件就是一个“struct input_event”结构体。
  4. 核心层可以决定把输入事件转发给上面哪个 handler 来处理:
    • 从 handler 的名字来看,它就是用来处输入操作的。有多种 handler,比如:evdev_handler、kbd_handler、joydev_handler 等等。
    • 最常用的是 evdev_handler:它只是把 input_event 结构体保存在内核buffer 等,APP 来读取时就原原本本地返回。它支持多个 APP 同时访问输入设备,每个 APP 都可以获得同一份输入事件。
    • 当 APP 正在等待数据时,evdev_handler 会把它唤醒,这样 APP 就可以返 回数据。
  5. 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_eventinclude/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 串口收发实验

回环收发

发表评论