进程终止

_exit是unix系统调用

exit_exit的c语言封装,它进行

  • 调用退出处理程序
  • 刷新stdio缓冲区
  • 调用_exit 从main函数返回一般即exit

解释器文件

Shell内建命令

将某一 shell 命令实现为内建命令,不外乎如下两个目的:效率以及会对 shell 产生副作用(side effect)。

一些频繁使用的命令(如pwd、echo和test)逻辑都很简单,放在shell内部实现效率会更高。

将其他命令内置于 shell实现,则是希望命令对shell本身能产生副作用:更改shell所存储的信息,修改shell进程的属性,亦或是影响shell进程的运行。例如,cd命令必须改变shell自身的工作目录,故而不应在一个独立进程中执行。产生副作用的内建命令还包括exec、exit、read、set、source、ulimit、umask、wait以及shell的作业控制(job-control)命令(jobs、fg和bg)。想了解shell支持的全套内建命令,可参考shell手册页(manual page)文档。

exec时fd会被保持

close-on-exec

内核调度实体

KSE

进程和线程都是KSE,它们的区别只在于共享资源的形式

多进程 vs 多线程

多线程无隔离性

  • 注意线程安全,使用线程安全版本的函数或者以线程安全的方式调用函数
  • 恶意线程可以占用大量资源,修改其他线程的内存空间

多线程易于数据共享,上下文切换开销小,创建快

pthread_join

线程之间平等peer,没有进程的父子关系

只能等待某一个特定的线程,不能像进程的wait一样模糊匹配

条件变量总是结合互斥量使用。条件变量就共享变量的状态改变发出通知,而互斥量则提供对该共享变量访问的互斥(mutual exclusion)。

为什么条件变量的惯常写法使用while而不是if

  • 通知发送到唤醒并不是原子的。换言之,中间可能发生了改变条件状态的事
  • 设计时设置“宽松的”判断条件或许更为简单。有时,用条件变量来表征可能性而非确定性,在设计应用程序时会更为简单。换言之,就条件变量发送信号意味着“可能有些事情”需要接收信号的线程去响应,而不是“一定有一些事情”要做。
  • 虚假唤醒

pthread_once

确保多线程中只进行一次初始化

库函数TSD改造

pthread_key_create为该库函数添加一个键,绑定该库函数的deconstructor。使用pthread_once保证这个注册过程只进行一次

每次库函数的调用中,首先pthread_getspecific查询是否已经为本线程创建缓冲空间,如果没有就创建并使用pthread_setspecific来记录

线程安全

可供多个线程安全调用

可重入(避免全局和静态)是线程安全的

线程特有数据TSD 静态局部

线程局部存储TLS 全局

在线程中fork exec是可以的,但需要注意fork到exec期间的异步安全(信号安全)

非异步安全函数依赖全局状态,如malloc是线程安全的,但它不是可重入的

异步安全

event driven

比如使用信号算是一种

more than non-blocking, non-blocking还是需要spin wait的

Problems with threads

  • Synchronization bugs (data races, deadlock, etc.)

  • Limits concurrency to number of threads

  • Stacks are expensive

  • Possible interleaving of threads is hard to reason about Problems with events

  • State across events can’t be stored on stack (“stack ripping”) coroutine

  • Possible interleaving of events is hard to reason about

  • Sequential execution is lost, has to be manually traced across code

  • Blocking I/O with threads is simple to write but doesn’t scale well

    • Number of outstanding operations number of threads
  • Asynchronous I/O allows a thread to have many I/O operations in parallel

    • Asynchronous because code handles completion at some later time, such that it is not synchronized with the start of the request
  • Asynchronous I/O leads to event-driven programming

  • Event-driven programming complicates sequential code

    • A linear series of I/O calls is spread across multiple functions, which a programmer must manually string together in their head
  • Coroutines are the standard C++ middle ground: looks like threads, but many coroutines can execute on a single thread

  • Other languages have different approaches

  • The tension between simplicity and performance has been an open challenge in systems code for the past 25 years

Scale out, not up