操作系统——进程,线程,锁

摘要:
一个进程的异常退出不会导致另一个进程异常运行;但是,如果线程异常退出,将导致整个进程崩溃。僵尸、孤儿、监护人孤儿进程-如果父进程退出,且其一个或多个子进程仍在运行,则这些子进程将成为孤儿进程。孤儿进程将被init进程采用,init进程将完成它们的状态收集。当进程终止时,管道中的数据将被删除。
基本概念

状态、地址空间

  1. 三种基本状态 —— 就绪、运行、阻塞

操作系统——进程,线程,锁第1张

  1. 进程控制块PCB(Process Control Block)

    1. 进程描述信息(如PID);
    2. 进程控制&管理信息(状态、优先级等);
    3. 源分配清单(地址空间状况、fd等);
    4. 处理其相关信息(各寄存器的值等)

进程存在的标识,在Linux系统中是task_struct,task_struct在内核栈(Linux进程氛围用户栈和内核栈)的尾端分配。

  1. 进程地址空间

从低地址到高地址:

  • text 代码段 —— 代码段,一般是只读的区域;
  • static_data 段 =
  • stack 栈区 —— 局部变量,函数的参数,返回值等,由编译器自动分配释放;
  • heap 堆区 —— 动态内存分配,由程序员分配释放;
    image
  1. 进程与线程

进程是拥有资源的基本单位,进程的地址空间相互独立;

线程是独立调度的基本单位,共享同一个进程内的资源(线程有自己的栈),减少了程序并发时所付出的时空开销,并且可以高效的共享数据,有效地利用多处理器和多核计算机,提高os的并发度。

一个进程异常退出不会引起另外的进程运行异常;但是线程若异常退出一般是会引起整个进程奔溃。

创建/撤销/切换 进程的开销远大于线程的(创建线程比创建进程快10~100倍 UNPv2/P406)。

  1. 僵尸、孤儿、守护

孤儿进程 —— 父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程 —— 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。

守护进程 —— 守护进程的父进程是init进程,因为它真正的父进程在fork出子进程后就先于子进程exit退出了,所以它是一个由init继承的孤儿进程。不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

最大进程数以及单进程内的最大线程数?

最大进程数受以下3方面限制:

  1. 不能超过pid_t类型的最大值
  2. 使用命令ulimit -u查看系统中限制的最大进程数。/etc/security/limits.conf里面是硬限制,ulimit -u是软限制,内核参数kernel.pid_max也做了限制。
  3. 受系统资源限制,创建一个新进程会消耗系统资源,最主要的就是内存。

IPC (interprocess communication)

UNP 分了以下几中形式的IPC:

  • 消息传递 —— 管道、FIFO、消息队列
  • 共享内存
  • 同步 ——信号量、互斥量、条件变量、读写锁、文件和记录锁、
  • 远程过程调用 —— solaris 门、Sun RPC
  • 跨网络 IPC —— 套接字
  • 域套接字
  • 信号

进程间通信方式:匿名管道,有名管道,消息队列,共享内存,信号量,套接字,域套接字,信号

线程同步方式:互斥量,条件变量,读写锁

进程间通信

匿名管道

半双工的(即数据只能在一个方向上流动),具有固定的读端和写端;是一种特殊的文件(pipefs,挂载在内核中),有固定大小,只存在于内存中;

实现原理:

管道是由内核管理的一个缓冲区,被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

在 Linux 中,管道的实现借助了文件系统的file结构和VFS的索引节点inode。通过将两个file结构指向同一个临时的VFS索引节点,而这个VFS索引节点又指向一个物理页面而实现的。

内核会利用一定的机制同步对管道的访问。
image

有名管道

半双工,可在无关进程使用;FIFO有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中

实现原理:

Linux中设立了一个专门的特殊文件系统--管道文件,FIFO在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,虽然FIFO在VFS的目录树下可见,但是它并不对应disk上的文件。
本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统来为管道命名。当删除FIFO文件时,管道连接也随之消失。当进程终止时,管道内的数据会被删除。

消息队列

  1. 面向记录的,其中的消息具有特定的格式以及特定的优先级;
  2. 独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除(随内核的持性)。
  3. 可以实现消息的随机查询,不一定要以先进先出的次序读取,也可以按消息的类型读取。
为什么不使用消息队列
  1. 进程终止时,消息队列及其内容并不会被删除(随内核的持性)。
  2. 在文件系统中没有名字,不能使用IO。

共享内存和信号量

共享内存是最快的:

通常往管道、FIFO或消息队列写入数据时,这些IPC需要将数据从进程复制到内核,通常总共需要复制4次,而共享内存则只拷贝2次数据;如图:
image
image

信号(Signal)

用于通知接收进程,有某种事件发生。

信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。

  • SIGRTMIN之前的信号是非排队(不可靠)的,多次连续发生的相同信号,只递交一次;SIGRTMIN之后的信号不会丢失,会递交多次。
  • 在信号处理函数
    • singal:只阻塞当前正在处理的信号,后续信号会排队(也会区分可靠和不可靠)
    • sigaction:可以设置阻塞正在处理的信号和其他型号

Pipe 与 FIFO 比较:

  1. pipe在特殊文件系统pipefs中(内核中),VFS 目录树下不可见;FIFO 在目录树下可见;
  2. 都有 inode,但没有磁盘镜像(disk image);
  3. Pipe 用于亲缘关系进程通信;FIFO 无此要求;
  4. 限制都包含两个:OPEN_MAX(一个进程在任意时刻打开的最大描述符,默认1024);还有PIPE_BUF(可原子性地往一个管道/FIFO 的最大数据量,默认 4K)

线程间同步

互斥锁(Mutex)

加锁原语,排他性访问共享数据,用于保护临界区。可细分为递归锁/非递归锁。
如果存在某个线程依然使用原先的程序

(即不尝试获得mutex,而直接修改共享变量),互斥锁不能阻止其修改。所以,互斥锁机制需要程序员自己来写出完善的程序来实现互斥锁的功能(以下锁 一样)。

image

条件变量(Condition Variable)

互斥锁用于上锁,条件变量用于等待,条件变量的使用是与互斥锁共通使用的。

条件变量学名叫管程(monitor)【From muduo P40】。

image

读写锁

读写锁也叫做 共享-独占锁,允许更高的并发度。

互斥量要么是锁住状态,要么是不加锁状态,而且一次只有一个线程对其加锁。
读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可用同时占有读模式的读写锁。

image

读写锁可以通过使用互斥锁和条件变量来实现。

自旋锁(spinlock)

在获取锁之前一直处于忙等(自旋)阻塞状态;常用于
锁被持有的时间短,且线程并不希望在重新调度上花费太多成本。当线程自旋等待锁变为可用时,CPU不能做其他事情。

故而自旋锁常作为底层原语,用于实现其他类型的锁。

记录锁

记录锁是读写锁的一种扩展类型,可用于亲缘关系或无亲缘关系的进程之间共享某个文件的读与写。被锁住的文件通过文件描述符进行访问,执行上锁的操作函数是fcntl,这种类型的锁通常在内核中维护。

记录锁的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区,即其锁定的是文件的一个区域或整个文件。

记录锁有两种类型:共享读锁,独占写锁。基本规则是:多个进程在一个给定的字节上可以有一把共享的读锁,但在一个给定字节上的写锁只能有一个进程独用。即:如果在一个给定的字节上已经有一把读或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能再对它加任何读锁。

死锁

  1. 死锁 —— 就是两个或多个进程被无限期地阻塞、相互等待的一种状态。
  2. 死锁的四个条件
    1. 竞争同一个资源
    2. 持有资源不释放
    3. 不能抢占资源
    4. 循环使用资源

处理死锁的策略:
一般来说,打破循环使用资源最容易,即顺序加减锁

image
银行家算法(死锁避免算法)。在资源动态分配过程中,防止系统进入不安全状态,以避免发生死锁。

数据库中会用到等待图进行死锁检测

  1. 死锁定理:
    通过将资源分配图简化的方法,来检测系统状态是否为死锁状态。
    当且仅当S状态的资源分配图不可完全简化时,S为死锁状态。

经典问题

  1. 生产者-消费者问题
  2. 读者-写者问题

Futex

在Linux下,信号量和线程互斥锁的实现都是通过futex系统调用。

免责声明:文章转载自《操作系统——进程,线程,锁》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇python中的str()与eval函数Kmplayer硬件解码高清视频优化设置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Ogre2.0 全新功能打造新3D引擎

不知当初是在那看到,说是Ogre2.0浪费了一个版本号,当时也没多想,以为没多大更新,一直到现在想做一个编辑器时,忽然想到要看下最新版本的更新,不看不知道,一看吓一跳,所以说,网络上的话少信,你不认识别人,别人张嘴就来,对别人也没损失,还可以装B下,靠. 从现在Ogre2.1的代码来看,大约总结下,更新包含去掉过多的设计模式,SoA的数据结构(用于SIMD...

分享一些 Kafka 消费数据的小经验

前言 之前写过一篇《从源码分析如何优雅的使用 Kafka 生产者》 ,有生产者自然也就有消费者。 建议对 Kakfa 还比较陌生的朋友可以先看看。 就我的使用经验来说,大部分情况都是处于数据下游的消费者角色。也用 Kafka 消费过日均过亿的消息(不得不佩服 Kakfa 的设计),本文将借助我使用 Kakfa 消费数据的经验来聊聊如何高效的消费数据。...

将dll文件注入到其他进程中的一种新方法

http://www.45it.com/windowszh/201212/33946.htm http://www.hx95.cn/Article/OS/201212/65095.html 我们知道将动态连接库注入到其他进程中有很多种方法。最常见的方法是使用钩子函数(Hook),但是这种方法主要有两个缺点:第一如果某个进程没有加载User32.dll,那么...

spark 作业调度

一、调度分类 调度分为两种,一是应用之间的,二是应用内部作业的。 (一)应用之间 我们前面几章有说过,一个spark-submit提交的是一个应用,不同的应用之间是有调度的,这个就由资源分配者来调度。如果我们使用Yarn,那么就由Yarn来调度。调度方式的配置就在$HADOOP_HOME/etc/hadoop/yarn-site.xml中 [html...

JDK10

1 局部变量类型推断 1.1 JDK10之前定义变量存在的问题 很多人抱怨Java是一种强类型,需要引入大量的样板代码。很明显类型声明往往被认为不是必要的。 JDK10之前的Java代码总中,声明一个变量是非常繁琐的: package com.sunxiaping; import org.junit.Test; import java.util.Ar...

【转】 .NET中STAThread和MTAThread

ref:http://blog.csdn.net/dyllove98/article/details/9735955 1 COM中的公寓 本文讨论进程内COM组件。以一个示例直观演示STAThread和MTAThread的作用和区别。 1.1 基本规则 公寓是COM组件的运行环境,日常生活中公寓是用来住人的,COM中的公寓是用来住COM组件的对象的,每个...