串口、链表等的常见问题以及好用工具技巧

介绍几个疑难问题及解决

Posted by Jerry Chen on December 2, 2020

串口自动行转换

问题介绍

Linux 串口调用 write 函数发送 2 字节(0x0d 0x0a),使用逻辑分析仪和示波器看到实际发送了 3 字节(0x0d 0x0d 0x0a),也就是 \n 自动被转换为 \r\n 再发送的;

问题的解决

原来 Linux 串口默认为“规范模式”,会自动行转换;

使用如下参数后可将串口配置为“原始模式”:

1
2
3
//修改输出模式,原始数据输出
new_uart_options.c_oflag &= ~OPOST;
new_uart_options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

扩展

串口还可配置等待时间和最小接收字符:

VTME 位数据设置为 150,表示串口无数据阻塞超时 15s;

1
2
3
//设置等待时间和最小接收字符
new_uart_options.c_cc[VTIME] = 150; /* 读取一个字符等待1*(1/10)s */
new_uart_options.c_cc[VMIN] = 0;    /* 读取字符的最少个数为1 */

此外,linux 串口默认 LSB 方式,即 0b’00000001 发送顺序为 <起始> 1 0 0 0 0 0 0 0 <校验> <停止>,可能是最通用的方式;

多线程下接收打印在发送打印前

问题介绍

线程1 接收帧并打印接收消息,线程 2 发送帧并打印发送消息;发送接收大量数据时,有时会先打印接收帧,然后才会打印发送帧;

1
2
3
4
5
6
7
//线程1
if(recv_frame())    //收到完整帧
	printf_frame(); //打印接收帧

//线程2
send_frame();       //发送帧
printf_frame();     //打印发送帧

问题的解决

发生打印顺序颠倒时执行顺序是这样的:线程 2 发送帧 –> 线程 1 打印接收帧 –> 线程 2 打印发送帧;

代码按如下方式修改即可:

1
2
3
4
5
6
7
//线程1
if(recv_frame())    //收到完整帧
	printf_frame(); //打印接收帧

//线程2
printf_frame();     //打印发送帧
send_frame();       //发送帧

链表节点被其他线程删除导致尾插节点丢失

问题介绍

线程 1 会删除被标记过时的节点,线程 2 尾插节点有时会导致节点丢失;

1
2
3
4
5
6
//线程1
del_old_node();		//删除旧节点,比如链表为 H->1->2,删掉 1 后剩下 H->2

//线程2
end_node = scan_end_node();	//查找到最后的节点
end_node->next = new_node;  //尾插新节点

问题的解决

发生尾插节点丢失时执行顺序是这样的:线程 2 查找到最后的节点 e –> 线程 1 删除过时的节点 e –> 线程 2 在节点 e 后插入新节点 n;

第一条链:H->1->2->e 变为 H->1->2;

第二条链:(null) 变为 e(内容随机,地址存在)->n;

代码中操作链表的地方加锁即可:

1
2
3
4
5
6
7
8
9
10
//线程1
do_node_mutex.lock();
del_old_node();		//删除旧节点,比如链表为 H->1->2,删掉 1 后剩下 H->2
do_node_mutex.unlock();

//线程2
do_node_mutex.lock();
end_node = scan_end_node();	//查找到最后的节点
end_node->next = new_node;  //尾插新节点
do_node_mutex.unlock();

扩展

互斥锁是服务于共享资源的,一般在线程中成对出现,同一时间只能有一个线程访问该资源;

信号量是服务于多线程间执行的逻辑顺序的,可在线程 1 中 wait,在线程 2 中 post,根据初值可设置同一时间最多有 n 个线程访问;

C 动态库无法被 C++ 工程链接

问题介绍

C++ 工程用到了动态库 t.so,链接时报错找不到 t.so 中的函数 tt;

1
2
3
4
5
6
7
8
9
10
11
12
//t.c(动态库 t.so 源文件)
#include "t.h"
void tt(void){...}

//t.h(动态库 t.so 头文件)
void tt(void);

//demo.cpp(C++ 工程)
#include "t.h"
void demo(){
    tt();
}

问题的解决

原因是因为 C 头文件和 C++ 头文件不通用,C++ 使用 C 头文件时需要加上关键字 extern "C"{...}

更加建议在动态库 C 头文件中加入如下宏(C 系统库头都有类似或者其他特殊处理):

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif
 
/*...*/
 
#ifdef __cplusplus
}
#endif

可执行文件 rpath 不合理

问题介绍

rpath 是可执行文件优先寻找依赖库的目录;

比如在 ubuntu 上交叉编译生成可执行文件时,cmake 脚本指定链接了 ../lib/a.so,但是希望可执行文件在 arm 上某些目录上查找动态库;

问题的解决

  • cmake 编译 target 时添加:

    给 target 设置 BUILD_RPATH 属性;

    1
    2
    
    SET_TARGET_PROPERTIES(demo
      PROPERTIES BUILD_RPATH ${CMAKE_BINARY_DIR})
    
  • 不操作可执行文件的方法:

    事先给环境变量 LD_LIBRARY_PATH 赋值;

    1
    
    export $LD_LIBRARY_PATH=/lib/test
    
  • 操作可执行文件的方法:

    需要 patchelf 工具,安装办法 sudo apt install binutils

    $ORIGIN/ 表示可执行文件所在路径;

    1
    
    patchelf --set-rpath '$ORIGIN/:/lib/:/usr/lib' demo
    

命令行传入密码让 sudo 提权

问题介绍

sudo xxx 执行时需要交互式输入密码,但有的时候需要脚本自动填写密码;

问题的解决

默认情况下 sudo 不接受标准输入方式填写密码;

解决办法:

1
echo your_password | sudo -S xxx
扩展,需要组合延迟输入的可以使用括号 “() xxx”,常见的如更改 root 密码的命令:
1
(echo abcd;sleep 1;echo abcd) | passwd

.gitignore 失效

问题介绍

修改 .gitignore 文件后,发现追踪的文件列表不符合预期;

问题的解决

更新 git 的缓存区;

解决办法:

1
2
3
4
5
6
7
8
# 在 git 项目根目录,清除缓存区
git rm -r --cached .

# 重新添加文件到 git 追踪
git add .

# 提交新的忽略文件
git commit -m "update .gitignore"

删除远程 git 仓库所有历史提交记录

问题介绍

某些情况下,希望删除远程 git 仓库的所有历史提交记录来保持仓库干净;

问题的解决

解决办法:新建分支 latest_branch,替换 master 分支;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1.Checkout
git checkout --orphan latest_branch
 
# 2. Add all the files
git add -A
 
# 3. Commit the changes
git commit -am "commit message"
 
# 4. Delete the branch
git branch -D master
 
# 5.Rename the current branch to master
git branch -m master
 
# 6.Finally, force update your repository
git push -f origin master

程序没有打印信息怎么跟踪调试

问题介绍

某些情况下,程序没有打印信息输出,怎么跟踪程序执行情况?

问题的解决

解决办法:使用 strace 命令;

1
strace ./xxx