现代操作系统引入了线程的概念。线程是CPU执行的最小单元,相对于进程而言,它具有轻捷、高效、开销小等优点。
14.2 线程和经典进程的比较
线程和经典进程的主要区别如下。
—
线程没有自己的独立空间,进程内的线程共用内存空间。进程具有自己的独立空间,进程之间无法直接访问对方数据。
—
各个线程的栈空间虽然独立,但位于同一进程内的线程,其栈空间仍然是从同一地址空间内分配,因此可以直接访问。这样,一个线程的栈空间损坏,可能会影响到进程内其他线程。而进程则一般不会出现该问题。因此从安全性而言,进程更好。
—
线程的执行实体是一个(多个)函数及相关数据、资源;而进程则是可执行文件。
—
线程的入口可以是任意一个符合指定格式的函数;而进程则是main函数(从程序员角度而言)。
—
线程必须属于某个进程,不能单独存在;而进程则不是。
—
线程间的切换开销更小,特别是属于同一进程的线程。
—
同一进程内的线程可通过共享全局变量通信;而进程间则不行(除非通过特殊的共享机制)。
但它们也有共同点:
—
线程和进程(这里指经典进程)都有自己的栈空间。
—
线程和进程都有优先级、CPU执行时间等属性,都是CPU执行的最小单元。
进程的空间分布如图14-1所示。
图14-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的取值可有SSYS、STRC和SWTED。而其他值是进程结构的。
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可为值SWAIT、SRUN、SSTOP或SIDL。p_stat等于SWAIT,当且仅当其包含的所有线程都处于SWAIT或SSLEEP状态。
proc结构中少了p_chan变量,因为进程不会挂起在某个资源上,而只是进程内线程会挂起在某个资源上。
3.u变量
--------------选自光盘文件/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;
|
线程的实现方案如下。
1.用户可动态创建、销毁、挂起、恢复、退出线程。
2.每个进程启动时,至少有一个线程,那就是主线程(入口函数是main)。程序可以创建其他线程,当线程执行函数返回后,线程也就退出了。当所有线程都退出后,进程才会退出,但有一种情况例外,那就是主线程退出。一旦主线程退出,进程就退出了,原因在后面会有解释。
3.每当线程退出时,它占用的用户栈会被释放,但分配的堆内存不会释放,这是由于当前内存分配时并没有线程记录(当然如果要加上也可以)。
4.每个线程都有自己的用户栈和内核栈,并且内核栈大小固定,不可调节;用户栈大小可由调用者在创建线程时设定,但线程运行期间也不可调节。当用户栈溢出时,MMU不能检测到它并触发栈违例自陷。但内核会定期检查它(频率是60HZ),从而触发SIGSTK信号。这样的实现是基于目前的虚存机制,如果采用页式虚存,则栈溢出监测和大小自动调节的实现会容易得多。而且PDP
11/40只提供了SLR寄存器来监测内核模式下的栈指针,对于用户模式下栈指针的监控,目前没有更好的手段。
5.一个线程不仅可以挂起、恢复或终止本进程内线程;而且可以挂起、恢复或终止其他进程内线程,但必须有root(超级用户)权限。
6.只有当进程内所有线程都处于挂起状态时,进程才被认为是挂起状态,否则处于运行状态。
7.当进程换出后,其数据段被释放,其内所有线程均无法运行。
上一章 进程间通信 目录 下一章 网络多用户