文章目录
线程标准
POSIX标准
- 一套接口规范,定义了操作系统(尤其是类Unix系统)应该提供哪些功能、以及如何调用这些功能
- 核心目的:可移植性。在遵循POSIX的不同系统(如Linux, BSD, macOS, VxWorks, QNX等)上都能编译运行,无需大量修改
- 嵌入式Linux中的意义:系统调用(如read, write, fork)、文件操作、进程线程API,其行为规范大都源于POSIX
POSIX线程(Pthreads)
- POSIX标准中定义的线程编程接口Pthreads,是一组C语言函数(以pthread_开头)、数据类型(如pthread_t)和常量
- 核心思想:在单个进程内,创建多个并发执行流,它们共享进程的大部分资源,但各自拥有独立的执行上下文(如栈、寄存器)
线程属性(POSIX.1标准)
| 类别 | 具体属性/资源 | 说明 | 嵌入式开发中的意义 |
|---|---|---|---|
| 共享属性 (进程级) | 全局内存 (数据段、堆段) | 线程间通信最直接的渠道。全局变量、malloc 分配的内存所有线程可见 |
需要同步机制(互斥锁)保护,否则会导致数据竞争 |
| 文件描述符 | 一个线程打开的文件,其他线程可直接读写 | 需注意文件偏移量的共享,以及close的时机 |
|
| 信号处置方式 | 信号处理函数(signal /sigaction设置)被所有线程继承 |
多线程下信号处理复杂,通常建议用专门的信号处理线程 | |
| 进程ID(PID)、用户ID等 | 整个进程只有一个身份 | 权限管理以进程为单位 | |
| 独有属性 (线程级) | 线程ID | pthread_t类型,系统内唯一标识一个线程。 |
用于pthread_join, pthread_cancel等操作。 |
| 独立的栈 | 用于存放自动变量(局部变量)、函数调用链 | 栈大小有限(默认几MB),嵌入式开发需注意深度递归可能导致的栈溢出 | |
errno变量 |
每个线程有自己的errno ,避免一个线程的错误影响另一个 |
这是Pthreads对传统errno 的重要改进 |
|
| 调度策略与优先级 | 每个线程可独立设置 | 嵌入式实时系统中至关重要,用于控制关键线程的响应性 |
Linux下线程实现演变
LinuxThreads
- 是原始的 Pthreads 实现,从 glibc 2.4 开始,不再支持此实现
- LinuxThreads 线程被实现为共享比平常更多的信息的进程
- 偏离 POSIX.1 规范的实现:
- LinuxThreads 线程(包括管理器线程)使用 ps() 显示为单独的进程
- 对 getpid() 的调用在每个线程中返回不同的值
- 在主线程以外的线程中调用 getppid() 会返回管理器线程的进程 ID
- 当一个线程使用 fork() 创建新的子进程时, 只允许创建子线程的线程在其上wait()
- 进程与调用 execve() 的线程具有相同的 PID
- 线程不共享用户 ID 和组 ID
- 线程不共享公共会话 ID 和进程组 ID
- ...
NPTL (Native POSIX Threads Library)
- 现代 Pthreads 实现
- 与 LinuxThreads 相比,NPTL 更符合 POSIX.1 规范的要求,并且在创建大量线程时具有更好的性能
- NPTL 从 glibc 2.3.2 开始提供,并且需要 Linux 2.6 内核中存在的功能
- 使用 NPTL 时,进程中的所有线程都放置在同一个线程组中
- 线程组的所有成员共享同一个 PID
- ...
任何主流嵌入式Linux发行版(如Buildroot, Yocto, OpenWrt等构建的),默认都是NPTL
可以认为线程就是POSIX标准的实现
确定线程实现的方式
- 可以使用getconf命令
bash
getconf GNU_LIBPTHREAD_VERSION
- 对于较旧的 glibc 版本:
bash
$( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | egrep -i 'threads|nptl'
Pthreads函数特点
关于返回值
- 返回值惯例:Pthreads与大多数Linux系统调用的重要区别
- 成功:返回 0。
- 失败:返回 正的错误号(如EAGAIN, EINVAL),返回的错误号与
errno中返回的错误号具有相同的含义 - 但是不设置errno:错误号直接通过返回值给出,所以不能用perror()直接输出错误,需要用strerror()转换
c
// 错误示例:perror("pthread_create"); // 这行不通!
// 正确做法:
int ret = pthread_create(&tid, NULL, func, NULL);
if (ret != 0) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
}
关于EINTR错误
- EINTR表示系统调用被信号中断
- 对于每个可能返回错误的
pthreads函数,POSIX.1-2001指定该函数永远不会因错误EINTR而失败- 即Pthreads函数本身不会被信号中断而失败
- 但普通的系统调用(如read, sleep, write)可能会被中断并返回EINTR
- 对可能阻塞的系统调用,在循环中处理EINTR是健壮性编程的常见做法
c
while ((n = read(fd, buf, size)) == -1 && errno == EINTR)
; // 什么也不做,只是重启read
if (n == -1) // 处理其他错误
perror("read");