运行->退出:导致进程终止的原因有:正常完成、超过时限、系统无法满足进程需要的内存空间、进程试图访问不允许访问的内存单元(越界)、算术错误(如除以0或存储大于硬件可以接纳的数字)、父进程终止(操作系统可能会自动终止该进程所有的后代进程)、父进程请求终止后代进程等。
1. 进程与线程的区别。
在多线程环境中,一个进程被定义成资源分配的单位和一个被保护的单位,与进程相关联的有:
(1)存放进程映像(程序、数据、栈和进程控制块中定义的属性的集合)的虚拟地址空间
(2)受保护的对处理器、其他进程(用于进程间通信)、文件和IO资源(设备和通道)的访问
在一个进程中,可能有一个或多个线程,每个线程有:
线程执行状态(运行、就绪等)
² 在未运行时保存的线程上下文
² 一个执行栈
² 用于每个线程局部变量的静态存储空间
² 与进程内的其他线程共享的对进程的内存和资源的访问
在多线程进程中,每个线程都有一个独立的栈,还有独立的线程控制块用于包含寄存器值、优先级和其他与线程相关的状态信息。
在大多数操作系统中,独立进程间的通信需要内核的介入,以提供保护和通信所需要的机制。但是,由于在同一个进程中的线程共享内存和文件,他们无需调用内核就可以相互通信。
2. 进程通信的几种方式。
进程间通信主要包括管道, 系统IPC(包括消息队列,信号量,共享存储), SOCKET。
管道包括三种:1)普通管道PIPE, 通常有种限制,一是半双工,只能单向传输;二是只能在父子进程间使用. 2)流管道s_pipe: 去除了第一种限制,可以双向传输. 3)命名管道:name_pipe,去除了第二种限制,可以在许多并不相关的进程之间进行通讯.
系统IPC的三种方式类同,都是使用了内核里的标识符来识别.
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号(signal) 机制和Linux信号量(semaphore)机制的区别 http://blog.youkuaiyun.com/langjian2012/article/details/39717903
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( sharedmemory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
3.线程同步几种方式。(一定要会写生产者、消费者问题,完全消化理解)
进程中线程同步的四种常用方式:
(1)临界区(CriticalSection)
当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式:
(1、 定义临界区对象
(2、 在访问共享资源(代码或变量)之前,先获得临界区对象
(3、 访问共享资源后,则放弃临界区对象
(2)事件(Event)
事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。比如在某些网络应用程序中,一个线程如A负责侦听通信端口,另外一个线程B负责更新用户数据,利用事件机制,则线程A可以通知线程B何时更新用户数据。
(3)互斥量(Mutex)
互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。
(4)信号量(Semphore)
当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。Semaphore类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个Semaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。
4.线程的实现方式. (也就是用户线程与内核线程的区别)
线程的实现可分为两大类,用户级线程(user-levelthread,ULT)和内核级线程(kernel-levelthread,KLT)。后者又称为内核支持的线程或轻量级进程。
(1)用户级线程
在一个纯粹的用户级线程软件中,有关线程管理的所有工作都由应用程序完成,内核意识不到线程的存在。
使用用户级线程而不是内核级线程的优点有:
- 线程切换不需要内核态特权,进程并不需要为了线程管理而切换到内核态
- 可以为应用程序量身定做调度算法而不扰乱底层的操作系统调度程序
- 用户级线程可以在任何操作系统中运行,不需要对底层内核进行修改以支持用户级线程
相比内核级线程,用户级线程有两个明显的缺点:
- 许多系统调用都会引起阻塞,当用户级线程执行一个系统调用时,不仅这个线程会被阻塞,进程中的所有线程都会被阻塞
- 在纯粹的用户级线程策略中,一个多线程应用程序不能利用多处理技术
(2)内核级线程
在一个纯粹的内核级线程软件中,有关线程管理的所有工作都是由内核完成的,应用程序部分没有进行线程管理的代码,只有一个到内核线程设施的应用程序编程接口(API)。
该方法克服了用户级线程方法的两个基本缺陷:内核可以同时把同一个进程的多个线程调度到多个处理器中;如果进程中的一个线程被阻塞,内核可以调度同一个进程中的另一个线程。相比用户级线程它的主要缺点是:把控制从一个线程传送到进程中的另一个线程时,需要到内核的状态切换。
某些操作系统采用组合用户级线程和内核级线程的方法,同一个应用程序中的多个线程可以在多个处理器上并行的运行,某个会引起阻塞的系统调用不会阻塞整个进程。如果设计正确,该方法会结合两种线程的优点,同时减少他们的缺点。
5.用户态和核心态的区别。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。
特权级显然是非常有效的管理和控制程序执行的手段,因此在硬件上对特权级做了很多支持,就Intel x86架构的CPU来说一共有0~3四个特权级,0级最高,3级最低,硬件上在执行每条指令时都会对指令所具有的特权级做相应的检查。虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不同,即权力的不同。运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。
当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。
用户态和内核态的转换
1)用户态切换到内核态的3种方式
a. 系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
b. 异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
c. 外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
2)具体的切换操作
从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,涉及到由用户态切换到内核态的步骤主要包括:
[1] 从当前进程的描述符中提取其内核栈的ss0及esp0信息。
[2] 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
[3] 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。