操作系统相关内容

操作系统

进程和线程有什么区别?

  • 进程是系统进行资源分配和调度的基本单位,线程是CPU调度和分派的基本单位
  • 线程依赖于进程存在,一个进程至少有一个线程
  • 进程有自己的独立地址空间,线程共享进程的地址空间
  • 进程是拥有系统资源的一个独立单位,而线程自己基本上不拥有系统资源,只有一些在运行中必不可少的资源(如程序计数器、一组寄存器和栈),和其他线程共享本进程的相关资源,如内存、I/O、CPU等
  • 在进程切换时,涉及到整个当前进程CPU环境的保存环境设置以及新被调度运行的CPU环境的设置,而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作

同一进程中的线程可以共享哪些数据?

  • 进程代码段
  • 进程的共有数据(全局变量、静态变量)
  • 进程打开的文件描述符
  • 进程的当前目录
  • 信号处理器/信号处理函数:对收到的信号的处理方式
  • 进程ID和进程组ID

进程间通信方式有哪些?

  • 管道

    • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立两个管道

    • 管道是内核里面的一串缓存

    • 一个进程向管道中写的内容被管道另一端的进程读出,写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据

    • 只能由于父子进程或者兄弟进程之间(具有亲缘关系的进程)

  • 命名管道

    • 可用于没有亲缘关系的进程间,通过mknode()系统调用或者mkfifo()函数建立命名管道。命名管道实质上也是通过内核缓冲区来实现数据传输。有访问权限的进程,可以通过磁盘的索引节点来读写这块缓冲区。

    • 当不再被任何进程使用时,命名管道在内存中释放,但磁盘节点仍然存在。

  • 消息队列

    • 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示,异步通信

    • 消息队列克服了信号传递信息少、管道只能承载无格式字节流及缓冲区大小受限等缺点,可传递有格式的数据,但大小仍有限制

    • 消息队列只有在内核重启或显示地删除一个消息队列时,该消息队列才会真正被删除

    • 与管道不同,消息队列在某一进程往一个队列写入消息前,不需要另一个进程在该队列上等待消息的到达

    • 消息队列不适合比较大数据的传输,在通信过程中,存在用户态与内核态之间的数据拷贝开销

  • 信号(Signal)

    • 信号事件来源有硬件来源(Ctrl C)和软件来源(kill命令),异步通信

    • 发送接收信号有信号队列,未执行则保存该信号直至恢复执行,阻塞则信号传递被延迟,直至阻塞取消再传给进程

    • 处理信号的三种过程:1、处理信号,定义信号处理函数,当信号发生时,执行相应的处理函数

    • 2、忽略信号,当不希望接收到的信号对进程的执行产生影响,而让进程继续执行时,可以忽略该信号,不对信号做任何处理

    • 3、执行默认操作,Linux对每种信号都规定了默认操作,有的信号如SIGSTOP、SIGKILL等无法忽略。

  • 共享内存

    • 不同进程可以将同一段共享内存映射到自己的地址空间,像访问正常内存一样访问它
    • 优点是简单高效,不需要系统调用,不涉及用户态到内核态的转换,也不需要对数据不必要的复制
    • 缺点存在并发问题,可能多个进程修改同一块内存,,因此一般与信号量结合使用
  • 信号量:初始化操作、P操作、V操作;P操作:信号量-1,检测是否小于0,小于则进程进入阻塞状态;V操作:信号量+1,若小于等于0,则从队列中唤醒一个等待的进程进入就绪态

    • 进程互斥访问共享内存,初始化信号量为1,多进程同步,初始信号量为0。
  • 套接字(Socket)

    • 不同的计算机进程之间通过socket通信,也可用于同一计算机的不同进程

    • 需要通信的进程之间首先各自创建一个socket,内容包括主机地址与端口号,声明自己接收来自某端口地址的数据

    • 操作系统提供创建socket、发送、接收的系统调用,为每个socket设置发送缓冲区、接收缓冲区

    • 根据创建socket类型的不同,通信方式也不同:针对TCP协议通信、针对UDP协议通信、针对本地进程通信

进程调度策略

1.批处理系统

  • 先来先服务(first come first served) - FCFS

按照请求顺序进行调度,非抢占式,开销小,无饥饿问题,响应时间不确定

  • 最短作业优先(shortest job first) - SJF

按估计运行时间最短的顺序进行调度,非抢占式,吞吐量高,开销较大,可能造成长进程一直等不到调度而导致进程饥饿

  • 最高响应比优先(highest response ratio next) - HRRN

响应比 =(等待时间+执行时间)/执行时间,同时考虑了等待时间的长短和估计需要执行时间的长短,很好的平衡了长短进程,非抢占式,吞吐量高,开销较大,能够提供好的响应时间,无饥饿问题

2.交互式系统

  • 时间片轮转(round robin) - RR

将所有就绪进程按照FCFS的原则排成一个队列,用完时间片的进程排到队列最后,抢占式,开销小,无饥饿问题,为短进程提供好的响应时间。若时间片小,进程切换频繁,吞吐量低;若时间片长,实时性得不到保证

  • 优先级调度算法

为每个进程分配一个优先级,按优先级进行调度,为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级

  • 多级反馈队列调度算法(multiple feedback queue) - MFQ

设置多个就绪队列1、2、3…,优先级递减,时间片递增。只有等到优先级更高的队列为空时,才会调度当前队列中进程,如果进程用完了当前队列的时间片还未执行完,则会被移到下一队列。抢占式,开销较大,对IO型进程有利,可能出现饥饿问题

什么叫优先级反转,如何解决

高优先级的进程等待被一个低优先级进程占用资源时,就会出现优先级反转,即优先级较低的进程比优先级较高的进程先执行

解决方法:

  • 优先级天花板(priority ceiling):当任务申请到资源时,把该任务的优先级提升到可访问这个资源的所有任务中的最高优先级
  • 优先级继承(priority inheritance):当任务A申请共享资源S时,如果S正被C使用,通过比较A与C任务的优先级,如果发现C的优先级小于自身,则将C的优先级提升到自身的优先级,任务C释放资源S后,再恢复任务C的原优先级

什么是僵尸进程?

当一个子进程比父进程先结束,而父进程没有回收子进程,释放子进程占用的资源,此时子进程成为一个僵尸进程。僵尸进程是一个已经死亡的进程,但是并没有真正被销毁,它已经放弃了几乎所有内存空间,也没有任何可执行代码,也不能被调度,仅仅在进程表中保留一个位置,记载该进程的ID、终止状态即资源利用信息供父进程收集,除此之外,不占有任何内存空间。该僵尸进程可能会一直留在系统中直到系统重启

危害:占用进程号,占用内存

以下情况不会产生僵尸进程:

  • 该进程的父进程先结束了。每个进程结束时,系统都会扫描是否存在子进程,若有,则用Init进程接管,成为该进程的父进程,并会调用wait等待其结束
  • 父进程调用wait或者waitpid等待子进程结束。 wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。waitpid则可以加入WNOHANG(wait-no-hang)选项,如果没有发现结束的子进程,就会立即返回,不会将调用waitpid的进程阻塞。同时,waitpid还可以选择是等待任一子进程(同wait),还是等待指定pid的子进程,还是等待同一进程组下的任一子进程,还是等待组ID等于pid的任一子进程
  • 子进程结束时,系统会产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程(注意:一般都需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束了,而信号处理函数只执行一次,所以要循环调用将所有结束的子进程回收);
  • 也可以用signal(SIGCLD, SIG_IGN)(signal-ignore)通知内核,表示忽略SIGCHLD信号,那么子进程结束后,内核会进行回收。

什么是孤儿进程

一个父进程以及结束了,但是它的子进程还在运行,那么这些子进程会成为孤儿进程。孤儿进程会被Init(进程ID为1)接管,当这些孤儿进程结束时由Init完成状态收集工作

线程同步有哪些方式?

线程同步: 线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。

  • 互斥量 Mutex:互斥量是内核对象,只有拥有互斥对象的线程才有访问互斥资源的权限。因为互斥对象只有一个,所以可以保证互斥资源不会被多个线程同时访问;当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源;
  • 信号量 Semaphore:信号量是内核对象,它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。信号量对象保存了最大资源计数当前可用资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0,就可以发出信号量信号,如果为0,则将线程放入一个队列中等待。线程处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量;
  • 事件 Event:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。事件分为手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。
  • 临界区 Critical Section:任意时刻只允许一个线程对临界资源进行访问。拥有临界区对象的线程可以访问该临界资源,其它试图访问该资源的线程将被挂起,直到临界区对象被释放。

什么是死锁?

在两个或者多个并发进程中,每个进程持有某种资源而又等待其它进程释放它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁(deadlock)。

死锁产生的必要条件?

  • 互斥:一个资源一次只能被一个进程使用;
  • 占有并等待:一个进程至少占有一个资源,并在等待另一个被其它进程占用的资源;
  • 非抢占:已经分配给一个进程的资源不能被强制性抢占,只能由进程完成任务之后自愿释放;
  • 循环等待:若干进程之间形成一种头尾相接的环形等待资源关系,该环路中的每个进程都在等待下一个进程所占有的资源。

死锁有哪些处理方法?

鸵鸟策略

直接忽略死锁。因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。

死锁预防

基本思想是破坏形成死锁的四个必要条件:

  • 破坏互斥条件:允许某些资源同时被多个进程访问。但是有些资源本身并不具有这种属性,因此这种方案实用性有限;
  • 破坏占有并等待条件:
    • 实行资源预先分配策略(当一个进程开始运行之前,必须一次性向系统申请它所需要的全部资源,否则不运行);
    • 或者只允许进程在没有占用资源的时候才能申请资源(申请资源前先释放占有的资源);
    • 缺点:很多时候无法预知一个进程所需的全部资源;同时,会降低资源利用率,降低系统的并发性;
  • 破坏非抢占条件:允许进程强行抢占被其它进程占有的资源。会降低系统性能;
  • 破坏循环等待条件:对所有资源统一编号,所有进程对资源的请求必须按照序号递增的顺序提出,即只有占有了编号较小的资源才能申请编号较大的资源。这样避免了占有大号资源的进程去申请小号资源。

Java线程状态

  • 新建状态(New):当线程对象创建时,即进入新建状态,如 Thread t = new MyThread();
  • 就绪/运行状态(Runnable)
    • 当调用线程对象的start()方法,如t.start()时,线程进入就绪状态。处于就绪状态,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是执行了t.start()此线程就会运行;
    • 当CPU开始调度处于就绪态的线程时,线程才真正执行,进入到运行态。(就绪态是进入到运行态的唯一入口,即线程若要进入运行态,首先必须要处于就绪态);
  • 阻塞状态(Blocked):处于运行态的线程可因为某种原因,暂时放弃对CPU的使用权,停止执行,即进入阻塞状态,直到其进入到就绪态,才有机会再次被CPU调用进入运行态。线程在获取synchronized同步锁失败(锁被其他线程占用),会进入同步阻塞状态;
    • 其他阻塞:通过调用线程的sleep()join()或发出来I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
  • 等待状态(Waiting):进入该状态表示当前线程需要等待其他线程做出一些特定的动作(通知或中断);
    • 当前线程运行时,其他线程调用join()方法时,当前线程进入等待状态;
    • 当前线程对象调用wait()方法时,进入等待状态;
    • 等待的线程被其他线程唤醒时,notify()notifyAll()方法,进入就绪态;
  • 超时等待(Timed_Waiting):与Waiting不同,其可以在指定的时间自行返回就绪态;
    • 调用静态方法,Thread.sleep(long)进入超时等待;
    • 线程调用wait(long)方法
    • 其他线程调用指定时间的join(long)方法
    • sleepyield
      • sleep(long)方法会使线程转入超时等待状态,时间到了之后才会转入就绪状态。而yield()方法不会将线程转入等待,而是强制线程进入就绪状态。
      • 使用sleep(long)方法需要处理异常,而yield()不用。
  • 终止状态(Terminated):线程执行完成或因异常退出了run()方法,该线程终止。
-------------本文结束感谢您的阅读-------------