传统的UNIX进程模型,操作系统中一切皆进程或文件,Linux继承了UNIX的设计哲学,现如今,线程是非常重要的,线程只是进程中的一条执行路径(a thread is merely an execution path within a process),线程共享进程虚拟内存中除stack段的一切资源,每个线程拥有私有的stack段,因为线程的stack段保存了线程的上下文,所以线程才真正实现了并发运行。
Linux可调度的实体是线程而不是进程(the kernel schedulable entity简称KSE),这其实也是Linux系统架构的一个核心内容,Linux中的任何线程(内核线程与用户线程),都映射到内核的元数据结构stack structre(没错,这就是进程的描述符)。
另一个需要牢记的点是CPU 支持的每个特权级别,每个线程都需要一个stack段,当代操作系统包含两个CPU的特权级别,非特权的用户模式与特权的内核模式(也称作用户空间和内核空间),因此,在Linux上的每个存活的用户态线程拥有两个stack段
- A user space stack: This stack is in play when the thread executes usermode code paths(线程执行用户太下的代码,启用用户态的stack段).
- A kernel space stack: This stack is in play when the thread switches to kernel mode (via a system call or processor exception) and executes kernel code paths (in process context)(线程通过系统调用切换到内核态执行内核代码时,启用内核态的stack段).
当然内核线程只有一个内核空间的stack段。
通过下图这个实例来对上述的内容进一步阐述
用户空间的虚拟内存中有三个进程,每个进程有1、3、2个线程,这些线程均拥有用户态的stack段,与这些线程相对应的是内核态的三个stack段和对应的内核态下的数据结构,除此之外是最底层只拥有内核态stack段的n个内核线程kthrd1、kthrd2、kthrdn。
通过一段bash脚本打印进程、线程、内核态线程、用户态线程
bash代码
[root@ct7_node02 tmp]# cat test.sh
#!/bin/bash
total_prcs=$(ps -A|wc -l)
printf "\nTotal # of processes alive = %9d\n" ${total_prcs}
# ps -LA shows all threads
total_thrds=$(ps -LA|wc -l)
printf "Total # of threads alive = %9d\n" ${total_thrds}
# ps aux shows all kernel threads names (col 11) in square brackets; count 'em
total_kthrds=$(ps aux|awk '{print $11}'|grep "^\["|wc -l)
printf "Total # of kernel threads alive = %9d\n" ${total_kthrds}
printf "Thus, total # of usermode threads alive = %9d\n" $((${total_thrds}-${total_kthrds}))
exit 0
[root@ct7_node02 tmp]# ./test.sh
Total # of processes alive = 152
Total # of threads alive = 166
Total # of kernel threads alive = 120
Thus, total # of usermode threads alive = 46
根据输出可以看到,当前的系统有152个进程(其实是32个进程152-120),当前操作系统一共为166个线程,其中120个线程是内核线程,剩余46个线程为32个进程以及这些进程创建的14(46-32)个线程。
那当前系统有多少个stack呢?120+46*2个(内核线程有1个stack,用户太的线程在用户态有一个stack,内核态也有一个stack)
stack是如何被创建的呢
If the process is multithreaded, it will have one user-mode thread stack per thread alive (including main()); The stacks are allocated either at the time of calling fork(2) (for main()) or pthread_create(3) (for the remaining threads within the process), which results in this code path being executed in process context within the kernel:
FYI, the pthread_create(3) library API on Linux invokes the (very Linuxspecific) clone(2) system call; this system call ends up calling _do_fork(); the clone_flags parameter passed along informs the kernel as to how exactly to create the 'custom process'; in other words, a thread!