进程
进程是怎样产生的?
早期的计算机系统只允许一次执行一个程序。是什么情况呢?就是你写好程序,载入计算机,然后输入,得到输出。下次还是这样。
现代计算机系统允许将多个程序载入内存,并发执行,所以要求对各种程序提供更严格的控制和更好的划分。这些需求产生了进程的概念,即执行中的程序。进程是现代分时系统的工作单元。(这是我抄的操作系统概念上的)
有这样一个面试题,程序和进程有什么区别?
一句话概括,进程是执行中的程序,是程序的一个实例。
进程是动态的,程序是静态的。程序包括了代码段,但是进程还包括了堆栈段和数据段。一个程序可以和多个进程对应,而这多个进程可以不一样,甚至可以完全不一样。
进程由代码,数据和进程控制块(PCB Process Control Block)组成。
我还是喜欢用比喻来解释进程。
猥琐的比喻
一个计算机系统就是一串进程,它们在等CPU为它服务。我们可以把这些进程看成人。最开始的批处理系统,一旦一个人占了CPU,那么他就让CPU给他服务完,不管时间长短。现在不同了,每个人让CPU服务一会,然后就得走,到后面再排队。
这个系统到底是怎么个情况呢?
这个系统里有好多队伍。有些人找CPU服务前,需要把礼物准备好,比如你得到硬盘里取点好东西给CPU看(哈哈,好东西),那先到硬盘那排队吧,硬盘队伍很长,因为要看好东西的人太多了,等着吧。
(进程有五个状态,new, stop, ready, run, wait, 前两个没用,ready是等待CPU的,run是在享受CPU的,wait是在等I/O或者其他事件的,每个I/O设备都会有个等待队列的)
一切准备都就绪了,才可以让CPU服务,这些人都在排队等CPU服务,如果CPU为每个人服务1分钟,那么公平的方法就是一个人被服务完后接着排到最后边。不过这个世界是不公平的哦。有的人马上就要等到CPU了,结果CPU说,你后边那个先过来(原来他爸是李刚)。还有更厉害的,你正在被服务着,后面突然来了一个人,说:“滚开,我先来,我爸拿着原子弹的钥匙“。好吧,让他先来吧。其实李刚的儿子比较惨,他大部分时间都在准备礼物,然后CPU只给他服务一小会,所以让他优先级高一点吧。
(进程调度算法,有些进程是cpu intensive的,有些是I/O intensive的,后面就是李刚的儿子,大部分时间都在等I/O,比如等待键盘输入,例如scanf("%d,%d",&a,&b); a=a+b; printf("%d\n",a);。这段代码的I/O时间远远大于CPU时间,所以给人家优先级高点吧。还有的人有他爸拿着原子弹钥匙,CPU要是不给他服务,那他把你炸了,这就是系统中一些重要性较高的事件,不处理会出问题。Linux里将优先级叫nice,nice越低,优先级越高,nice越高,优先级越低,其实就是好人的优先级低的意思,哈哈)
这些个人是怎么来的呢?男娲造的。最开始只有男娲。男娲造了几个人。然后这些人又造人,注意哦,这些人只有父亲没有母亲,一个人就可以生孩子了。与我们这些地球人不同,生了孩子不是为了养,而是为了让他们干活,可恶啊。孩子多了,可以多占CPU啊。举个最简单的例子,原先队伍有10个人,而且这10个人都是屁民,平分CPU。但是我太聪明了,我马上生了90个孩子,现在100个人平分,我和我的孩子占了91/100的CPU,哈哈哈哈哈哈哈。不过这时候CPU受不了了,被这一家子蹂躏死了。于是上帝又派了几个CPU去,并且一个CPU同时服务多个人(我想到了一个很邪恶很邪恶的比喻)。
(最开始只有一个进程,它创建了其他几个进程,然后这些进程又创建子进程。在实际的系统中,有些父进程与子进程关系好,比如Linux中使用fork后子进程继续使用父进程的代码,继承父亲的愿望。有些子进程产生后就与父亲断绝关系,比如fork后使用exec把原来的内存空间全给替换了,没有了父亲的基因。)
线程
为什么会出现线程呢?
用多进程完成一项任务时,它们往往需要合作,需要共享数据和通信。而进程之间进行通信和数据共享是比较麻烦的,而且开销比较大。而且多进程的产生不是为了让多进程共同完成一件事,是为了可以同时干很多事。而多线程是为了同时干一件事。
在操作系统概念上提到了4点多线程编程的优点:
响应度高:一个交互程序使用多线程,例如多线程web浏览器,一个线程载入图像,一个与用户交互。(和多进程比优势不明显)
资源共享:线程默认共享所属进程的内存和资源
经济:创建进程所需要的内存和资源分配比较昂贵。而线程创建和切换十分快。例如,对于Solaris,进程创建比进程切换要慢30倍,而进程切换要比线程切换慢5倍。
多处理器体系结构的利用:多线程的优点之一是充分使用多处理器体系结构。(优势不明显)
线程是CPU使用的基本单元,它由线程ID,程序计数器,寄存器集合和栈组成。它与属于同一进程的其他线程共享代码段,数据段和其他操作系统资源,如打开文件和信号。
多线程模型
操作系统上的多线程模型讲的比较抽象,难于理解。尤其是它讲的是一般意义上的模型,而不针对特定操作系统。比如有些人在理解用户线程和内核线程时,老是结合Linux来理解,这样就把概念混了。
用户线程:受内核支持,而无需内核管理
内核线程:直接由操作系统支持和管理
在用户线程和内核线程之间必然存在一种联系。
用户线程和内核线程有三种关系:多对一,一对一,多对多。
上面几句话是操作系统概念里的,容易让人产生误导,比如有人认为,多对一模型里,用户层有多个线程,而且内核层还有一个线程,实际上不是这样的。
多对一模型
将多个用户级线程映射到一个内核线程。线程管理在用户空间进行,效率较高。但是如果一个线程执行了阻塞系统调用,那么整个进程会阻塞。而且,任何时刻只有一个线程使用CPU,多个线程也不能运行在多CPU上。
什么意思呢?先不考虑线程,内核眼里只有进程,而且是重量级进程。而这其中的进程有些是多线程的。比如现在有3个进程A,B,C。其中A,B是单线程,C有3个线程。
那么,CPU是在A,B,C之间平分的,比如每个人用5秒,轮到C了,CPU一看,我靠,有三个人要蹂躏我,CPU说,你们上吧,一次只能一个,但是只有5秒钟。这三个人说,我擦,太快了,有个老大说,你们靠边,我来。。。
一对一模型
将每个用户线程映射到一个内核线程。该模型在一个线程执行阻塞系统调用时,能允许另一个线程继续执行。它能允许多个线程并行的运行在多处理器系统上。缺点是一个用户线程就对应着一个内核线程,而且这种线程在创建和切换时开销较大。而且总的线程数是受限制的。
这种模型是什么模型呢?还是拿三个进程A,B,C举例子。C有三个线程,C1,C2,C3。在一对一的模型中,CPU看到的是A, B, C1,C2,C3这样五个东西,这里进程C占了3/5的CPU。这不是和进程一样吗?实际上就是进程,这样的线程称为轻量级线程。
Linux里面就是这种模型。Linux的clone函数在创建进程时有很多参数,可以选择共享地址空间等。比如下面4个参数:
CLONE_FS 共享文件系统信息 CLONE_SIGHAND 共享信号处理程序 CLONE_VM 共享内存空间 CLONE_FILES 共享打开文件列表
Linux里的线程就是通过指定这几个参数来创建的。
多对多模型
还是用A, B, C的例子,这次C有4个线程,假设对应2个内核线程,CPU看到的是这样的东西。A, B , C1, C2。而实际C的四个线程是c1,c2,c3,c4。那么轮到C1了,4个人可以上,但是得一个一个的,轮到C2了,4个人又可以上了。假如c1在C1阶段将CPU惹生气了,需要买点礼物给CPU,CPU不把气撒在c1上,因为它不知道c1,它只知道C1,因此CPU生C1的气,等C1给我礼物我再让你享受,C1里面实际上是c1要去买礼物,但是这样就连累了c2,c3,c4。不过,咱们是多对多,C1被 CPU抛弃了,还有C2呢,于是c2,c3,c4就在C2阶段享受CPU了。
上面是多对多的一般模型。还有一种模型称为二级模型。
是什么意思呢?
上面的c1,c2,c3,c4在C1,C2里都能跑。现在不行了,c1,c2,c3可以在C1上跑,c4与C2绑定了。从宏观上讲,这个进程是多对多,但是里面实际上存在一对一,或者多对一,还有一般的多对多。