POSIX 安全概念

Posted 菜=原罪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了POSIX 安全概念相关的知识,希望对你有一定的参考价值。

查看 POSIX API 使用说明时,通常会遇到一些 安全性的概念;
本文将举例说明 glibc 2.37 release 中几种 安全概念,避免某些 glibc 库函数 ( 如 malloc ) 的错误姿势。

一. MT-Safe

Multi Thread Safe,很好理解,函数可以 同时在不同线程内执行
注意,MT-Safe 只是说 “可以同时在不同线程内执行”,不是说函数具有原子性 (atomic) ,或者函数内部使用了某种 内存同步机制;最常见的 MT-Unsafe 例子是

char * strerror (int errnum) //一个线程转换中的 errno 可能被 另一个线程 改变
Preliminary: | MT-Unsafe race:strerror | AS-Unsafe heap i18n | AC-Unsafe mem

可重入 (reentrant ) 是 MT-Safe 的 真子集,可重入函数 同时被两个线程调用时,“由于没有共用数据,或不修改共用数据”,执行结果不会相互影响;
我尝试多线程调用 strerror(errno),但并没有崩溃,没能触发 MT-Unsafe 的场景;

二. AS-Safe

Asynchronous Signal Safe,信号处理回调中可以 调用;
Signal 是软中断,会打断进程的运行,信号处理过程中,可能会在改变如 errno 或 浮点模式 等 进程的运行环境,异步信号安全(AS-Safe)函数 会在返回进程控制流前,恢复信号处理前的进程环境,从而保证进程执行的安全;最常见的 AS-Unsafe 例子依旧是

char * strerror (int errnum)
Preliminary: | MT-Unsafe race:strerror | AS-Unsafe heap i18n | AC-Unsafe mem 
char * strerror_r (int errnum, char *buf, size_t n) // 即便 线程安全,依旧
Preliminary: | MT-Safe | AS-Unsafe i18n | AC-Unsafe

其实 绝大多数 函数 都是 AS-Unsafe 的 …

三. AC-Safe

Asynchronous Cancellation Safe,异步撤销 启用后,依旧安全;
POSIX 标准 的众多接口中,只有三个是 AC-Safe 的:pthread_cancel, pthread_setcancelstate 和 pthread_setcanceltype. GNU C 与 POSIX 保持一致;
The routines cannot assume a floating point environment,particularly when asynchronous cancellation is enabled. If the configuration of the floating point environment cannot be performed atomically then it is also possible that the environment encountered is internally inconsistent.
什么是 异步撤销?待日后有空再回头 填坑;

POSIX多线程—线程基本概念

http://blog.csdn.net/livelylittlefish/article/details/7957007

作者:阿波
链接:http://blog.csdn.net/livelylittlefish/article/details/7957007

(整半年没有更新,发几篇以前的读书笔记。)

 

content

1. 线程建立与使用

  • 创建线程
  • 初始线程
  • 线程分离

2. 线程生命周期

  • 就绪态
  • 被阻塞
  • 线程终止
  • 线程回收

 

1. 线程建立与使用

 

创建线程

  • 通过pthread_create()函数创建线程;
    • 向该函数传递线程函数地址和线程函数参数;
    • 线程函数只有一个void*参数;
    • 该函数返回pthread_t类型的线程ID
  • 一般调用该函数创建线程,然后调用pthread_join()函数等待线程结束;
  • 在当前线程从函数pthread_create()中返回以及新线程被调度执行之间不存在同步关系;
    • 新线程可能在当前线程从pthread_create()返回值前就运行了;
    • 或在当前线程从pthread_create()返回之前,新线程就可能已经运行完毕了;

 

pthread_join()

  • 阻塞其调用者直到指定线程终止,然后可以选择地保存线程的返回值;
  • pthread_join()调用返回时,被连接线程就已经被分离(detached),再也不能连接该线程了;
  • 如果连接(joining)线程不关心返回值,或者它知道被连接(joined)的线程根本不返回任何值,则可向pthread_join()&retval参数传递NULL,此时,被连接线程的返回值将被忽略;

 

如何使用可参考pthread_create()pthread_join()手册;

 

初始线程

  • C程序运行时,首先运行main()函数,main()函数所在线程称为初始线程或主线程;
  • 初始线程可调用pthread_self()获得其ID,也可调用pthread_exit()来终止自己;
    • main()返回将导致进程终止,也将使进程内所用线程终止;
    • main()中调用pthread_exit(),这样进程就必须等待所有线程结束后才能终止;
  • 若初始线程将其ID保存在一个其他线程可以访问的空间,则其他线程就可以等待初始线程的终止或者分离初始线程;

 

线程分离

  • 分离一个正在运行的线程不会对线程带来任何影响,仅仅是通知系统当该线程结束时,其所属资源可以被回收;
  • 分离线程意味着通知系统不再需要此线程,允许系统将分配给它的资源回收;
  • 一个没有被分离的线程终止时会保留其虚拟内存,包括堆栈和其他系统资源。

 

2. 线程生命周期

 

在任意时刻,线程处于下表的四个基本状态之一。

 

状态

说明

就绪ready

线程能够运行,但在等待可用的处理器;

  • 可能刚刚启动
  • 或刚刚从阻塞中恢复
  • 或被其他线程抢占

运行running

线程正在运行;在多处理器系统中,可能有多个线程处于运行状态;

阻塞blocked

线程由于等待处理器外的其他条件无法运行,如条件变量的改变、加锁互斥量或IO操作结束;

终止terminated

不是被分离,也不是被连接,一旦线程被分离或者连接,它就可以被回收;

  • 线程从起始函数中返回
  • 或调用pthread_exit
  • 或被取消,终止自己并完成所有资源清理工作;

 

线程状态转换如下图。

技术分享

说明

  • 线程开始处于就绪状态;
  • 当线程运行时,它调用特定的起始函数;
  • 它可能被其他线程抢占,或者因等待外来事情而阻塞自己;
  • 最终线程完成工作,或者从起始函数返回,或者调用pthread_exit函数,即进入终止状态;
  • 如果线程已被分离,则它立刻被回收重用;否则,线程停留在终止状态直到被分离或被连接;

 

就绪态

  • 线程刚被创建时;
  • 线程被解除阻塞再次可以运行时;
  • 运行线程被抢占时,如时间片到;

 

被阻塞

  • 试图加锁一个已经被锁住的互斥量;
  • 等待某个条件变量;
  • 调用singwait等待信号;
  • 执行无法立即完成的IO操作;
  • 内存页错误之类的系统操作;

 

初始线程(main()函数所在线程)与普通线程区别

  • 初始线程的启动函数main()是从程序外部调用的;如crt0.o文件复制初始化进程并调用main()函数;而普通线程的启动函数及其运行参数均由pthread_create()函数创建线程时传入,且由CPU调度的;
  • main()函数的参数是argcargv;普通线程的参数是void*,且由pthread_create()函数传入;
  • 若普通线程从启动函数中返回,则线程终止,而其他线程依然可以运行;但初始线程从main()返回时,进程终止,进程内所有线程也被终止;
    • 若希望在初始线程终止时,进程中的其他线程继续执行,则需要在初始线程调中调用pthread_exit()而非从main()返回;
  • 大多数系统,初始线程运行在默认进程堆栈上,该堆栈可以增长到足够尺寸;而某些实现中,普通线程的堆栈空间是受限的;
    • 如果线程堆栈溢出,则程序会出现段错误;

 

线程睡眠原因

  • 被阻塞,需要的某个资源不可用;
  • 被抢占,即系统将处理器分配给其他线程;

 

pthread_join()的详细解释

  • 用来等待一个线程的结束;
    • 是一个线程阻塞函数,调用它的函数将一直等待到被等待的线程结束为止;
    • 如,主线程调用pthread_join()等待它创建的线程运行结束,即主线程调用该函数后会被阻塞;
    • 当函数返回时,被等待的线程的资源被回收;
  • 若此时新线程没有运行,则它将在主线程被阻塞后从就绪态进入运行态;当新线程运行完毕并返回时,主线程才会被解除阻塞,返回就绪态;当处理器可用时,主线程或立即执行或等到创建的线程终止后重新运行直到结束;

 

线程终止

  • 一般地,线程从启动函数返回来终止自己;
  • 当调用pthread_exit()退出线程或者调用pthread_cancel()取消线程时,线程在调用每个清理过程后也进入终止状态;
  • 清理过程又线程通过pthread_cleanup_push()注册,且尚未通过pthread_cleanup_poo()删除;

 

Linux系统僵尸线程

  • 如果线程已经被分离,则会被回收;否则,线程处于终止状态,仍然可以被其他线程调用pthread_join()连接;
  • 这种线程被称为僵尸线程,像Unix系统中的进程已经结束但还没有被一个wait/waitpid调用回收一样,即使已经死了但还存在;
  • 僵尸线程可能会保留其运行时的大部分甚至所有资源,因此不应该让线程长时间处于这种状态;当创建不需要连接的线程时,应该使用detachstate属性建立线程使其自动分离;

 

线程回收

  • 如果使用detachstate属性(即设置属性为PTHREAD_CREATE_DETACH)建立线程,或者调用pthread_detach()分离线程,则当线程结束时将被立刻回收;
  • 如果终止线程没有被分离,则它将一直处于终止状态直到被分离(通过pthread_detach)或者被连接(通过pthread_join)
  • 线程一旦被分离,就不能再访问它;
  • 回收将释放所有在线程终止时未释放的系统和进程资源,包括
    • 保存线程返回值的内存空间、堆栈;
    • 保存寄存器状态的内存空间;
    • 实际上线程终止时上述资源就不能被访问了;
  • 一旦线程被回收,线程ID就无效了,不能再连接、取消或者执行其他任何操作;
    • 终止线程ID可能被分给新线程;

NPTL (Native POSIX Threads Library)

This is the modern Pthreads implementation.  By comparison with LinuxThreads, NPTL provides closer conformance  to  therequirements of the POSIX.1 specification and better performance when creating largenumbers of threads.  NPTL is available since glibc 2.3.2, and requires features that are present in theLinux 2.6 kernel.

Thread-safe functions

A thread-safe function is one that can be safely (i.e., it will deliver the same results regardless of whetherit is) called from multiplethreads at the same time.

Reference

# man pthreads

# man pthread_create

# man pthread_join

# man pthread_exit

# man pthread_cancel


以上是关于POSIX 安全概念的主要内容,如果未能解决你的问题,请参考以下文章

POSIX 安全概念

在 Linux 内核命名空间之间使用 POSIX 信号量

linux c编程:Posix信号量

C - POSIX:共享内存

C++11 的 std::thread 是不是与 POSIX 信号量兼容?

在c中暂停posix线程特定时间间隔的问题