《返璞归真--UNIX技术内幕》--第14章 多线程的实现


14.1 

现代操作系统引入了线程的概念。线程是CPU执行的最小单元,相对于进程而言,它具有轻捷、高效、开销小等优点。

14.2  线程和经典进程的比较

线程和经典进程的主要区别如下。

     线程没有自己的独立空间,进程内的线程共用内存空间。进程具有自己的独立空间,进程之间无法直接访问对方数据。

     各个线程的栈空间虽然独立,但位于同一进程内的线程,其栈空间仍然是从同一地址空间内分配,因此可以直接访问。这样,一个线程的栈空间损坏,可能会影响到进程内其他线程。而进程则一般不会出现该问题。因此从安全性而言,进程更好。

     线程的执行实体是一个(多个)函数及相关数据、资源;而进程则是可执行文件。

     线程的入口可以是任意一个符合指定格式的函数;而进程则是main函数(从程序员角度而言)。

     线程必须属于某个进程,不能单独存在;而进程则不是。

     线程间的切换开销更小,特别是属于同一进程的线程。

     同一进程内的线程可通过共享全局变量通信;而进程间则不行(除非通过特殊的共享机制)。

但它们也有共同点:

     线程和进程(这里指经典进程)都有自己的栈空间。

     线程和进程都有优先级、CPU执行时间等属性,都是CPU执行的最小单元。

进程的空间分布如图14-1所示。

《返璞归真--UNIX技术内幕》--第14章  多线程的实现

14-1  进程空间分布示意图

 

14.3  线程的示例实现

14.3.1  相关数据结构

1.线程结构

线程结构必定包含优先级、执行时间等成员。


--------------选自光盘文件/usr/personal/kernel/include/Thread.h-------------
//系统中最大线程数

#define NTHREAD 256

 /* stat codes */

 #define SSLEEP 1 /* sleeping on high priority */
 #define SWAIT 2 /* sleeping on low priority */
 #define SRUN 3 /* running */
 #define SIDL 4 /* process/thread being created */
#define SSTOP 6 /* thread being traced */

 /* flag codes */

#define SLOAD 01 /* in core */
 #define SSYS 02 /* system thread/process */
 #define SLOCK 04 /* process cannot be swapped */
 #define SSWAP 010 /* process is being swapped out */
 #define STRC 020 /* thread is being traced */
 #define SWTED 040 /* another tracing flag */
#define STERM    100        //线程要被终止


typedef int (*ThreadProc)(int *pParam);

/*
 * 线程处理结构
 */

struct Thread
{
  char t_stat;
  char t_flag;
  char t_pri; /* priority, negative is high */
  char t_time; /* resident time for scheduling */
  char t_cpu; /* cpu usage for scheduling */
  char t_nice; /* nice for scheduling */
  int t_tid; /* unique thread id */
  int t_ttyp; /* controlling tty */
  int t_index; /* internal index from 0 to NTHREAD_PROC-1 */
  int t_pid; /* process id of parent */
  int t_ustackaddr; /* address of stack in userspace */
  int t_ustacksize; /* size of userstack (*64 bytes) */
  int t_wchan;/* event thread is awaiting */
  char t_name[12];
  ThreadProc t_entry;/* pointer to user entry */
  int *t_param; /* param passed to thread proc */
  struct proc *t_proc; /*parent process pointer */
}Threads[NTHREAD];

t_flag的取值可有SSYSSTRCSWTED。而其他值是进程结构的。

2.进程结构

// 每个进程内最大线程数,由于内存空间限制所致。如内存空间足够,可调为更大的值。



---------------选自光盘文件/usr/personal/kernel/include/proc.h---------------
#define NTHREAD_PROC        4     

#define SZOMB             5 /* process being terminated */

proc结构改为:
struct proc
{
  char p_stat;
  char p_flag;
  char p_nice; /* nice for scheduling */
  char p_time; /* resendial time for scheduler */
  char p_sig; /* signal number sent to this process */
  char p_uid; /* user id, used to direct tty signals */
  int p_ttyp; /* controlling tty */
  int p_pid; /* unique process id */
  int p_ppid; /* process id of parent */
  int p_addr; /* address of swappable image */
  int p_size; /* size of swappable image (*64 bytes) */
  int *p_textp;/* pointer to text structure */
  struct Thread *p_threads[NTHREAD_PROC];
  int p_threadNum;    //总线程数

  int p_actThreadNum; //运行着的线程数

  int *p_thdsysentry; /* pointer to sys entry ThreadSysEntry*/
} proc[NPROC];


p_stat可为值SWAITSRUNSSTOPSIDLp_stat等于SWAIT,当且仅当其包含的所有线程都处于SWAITSSLEEP状态。

proc结构中少了p_chan变量,因为进程不会挂起在某个资源上,而只是进程内线程会挂起在某个资源上。

3u变量


--------------选自光盘文件/usr/personal/kernel/include/Thread_U.h------------
#define USIZE                    (4096/64)
#define THREAD_SSIZE            1024
#define SSIZE                    4096    
/*
 * 线程上下文
 */

struct U_Thread
{
     int u_rsav[2]; /* save r5,r6 when exchanging stacks */
     int u_fsav[25]; /* save fp registers */                    
    /* rsav and fsav must be first in structure */
     char u_segflg; /* flag for IO; user or kernel space */
     char u_error; /* return error code */
     char u_uid;/* effective user id */
     char u_gid;/* effective group id */
     char u_ruid;/* real user id */
     char u_rgid;/* real group id */
     int u_threadp;/* pointer to Thread structure */
     char *u_base; /* base address for IO */
     char *u_count; /* bytes remaining for IO */ //read/write to/from u_base

     char *u_offset[2];/* offset in file for IO */
     int *u_cdir; /* pointer to inode for current directory */
     char u_dbuf[DIRSIZ];/* current pathname component */
     char *u_dirp; /* current pointer to inode */
     struct {/* current directory entry */
     int     u_ino;        //the directory u_name's inode number in the disk.

     char u_name[DIRSIZ];
     } u_dent;
     int *u_pdir;/* inode of parent directory of dirp */
     int u_ofile[NOFILE]; /* pointers to file structures of open files */
     int u_arg[5]; /* arguments to current system call */
 
     int u_qsav[2]; /* label variable for quits & interrupts */
     int u_ssav[2]; /* label variable for swapping */
     int u_utime;/* this process user time */
     int u_stime;/* this process system time */
     int u_cutime[2]; /* sum of childs’ utimes */
     int u_cstime[2]; /* sum of childs’ stimes */
     int *u_ar0;/* address of users saved R0 */
     int u_prof[4]; /* profile arguments */
     char u_intflg; /* catch intr from sys */
     char u_pad;
     char u_kernstack[802]; // 每个线程的内核栈

};

/*
 * 进程u变量,其总大小大约4K, 位于内核虚拟地址[0o140000, 0o150000]。
 */

struct
{
    //所有线程的“u变量”

    struct U_Thread u_threads[NTHREADS_PROC];

    //signal是进程级的,但处理过程运行在线程上下文中

    int u_signal[NSIG];/* disposition of signals */
    
    //新创建线程默认的u/gid

    char u_uid;/* effective user id */
    char u_gid;/* effective group id */
    char u_ruid;/* real user id */
    char u_rgid;/* real group id */

    int u_uisa[16]; /* prototype segmentation addresses */
    int u_uisd[16]; /* prototype segmentation descriptors */
    int u_tsize;/* text size (*64) */
    int u_dsize;/* data size (*64) */
    int u_ssize;/* stack size (*64) */

    //线程栈分配记录,为了提高效率,可采用链表实现。这里简单起见,采用数组。

    struct map u_stackmap[NTHREAD_PROC];
    int u_sep;/* flag for I and D separation */
    struct proc *u_procp;

    int u_curthd;    //Index for current thread structure in u_threads

}u;



 

14.3.2  实现方案

线程的实现方案如下。

1.用户可动态创建、销毁、挂起、恢复、退出线程。

2.每个进程启动时,至少有一个线程,那就是主线程(入口函数是main)。程序可以创建其他线程,当线程执行函数返回后,线程也就退出了。当所有线程都退出后,进程才会退出,但有一种情况例外,那就是主线程退出。一旦主线程退出,进程就退出了,原因在后面会有解释。

3.每当线程退出时,它占用的用户栈会被释放,但分配的堆内存不会释放,这是由于当前内存分配时并没有线程记录(当然如果要加上也可以)。

4.每个线程都有自己的用户栈和内核栈,并且内核栈大小固定,不可调节;用户栈大小可由调用者在创建线程时设定,但线程运行期间也不可调节。当用户栈溢出时,MMU不能检测到它并触发栈违例自陷。但内核会定期检查它(频率是60HZ),从而触发SIGSTK信号。这样的实现是基于目前的虚存机制,如果采用页式虚存,则栈溢出监测和大小自动调节的实现会容易得多。而且PDP 11/40只提供了SLR寄存器来监测内核模式下的栈指针,对于用户模式下栈指针的监控,目前没有更好的手段。

5.一个线程不仅可以挂起、恢复或终止本进程内线程;而且可以挂起、恢复或终止其他进程内线程,但必须有root(超级用户)权限。

6.只有当进程内所有线程都处于挂起状态时,进程才被认为是挂起状态,否则处于运行状态。

7.当进程换出后,其数据段被释放,其内所有线程均无法运行。


上一章   进程间通信                     目录                                   下一章 网络多用户

上一篇:和Steve之间的通信--RE: 关于《UNIX技术内幕》的勘误及遇到的问题_15


下一篇:和Steve之间的通信--RE: 关于《UNIX技术内幕》的勘误及遇到的问题_17