1.Linux下进程
1.1 Linux下进程
1.性质
- 1.每一个进程都有一个 PCB ,其是一个描述了进程所有详细信息的结构体,比如 pid 、 栈信息 等;
- 2.程序是静态表现(占用物理磁盘空间);进程是该程序运行后的动态表现(占用物理内存空间,具有CPU使用权);
2.进程的组成结构
从高地址到低地址分别是: PCB(4G~3G) 、 环境变量/命令行参数(从3G开始) 、 栈 、 静态库/共享库 、 堆 、 BSS 、 DATA 、 TEXT(代码段) ;
- 1.4G~3G 称为 Kernel 层,用户不可使用; 3G~1G 称为 User 层;
- 2.述的形态是一个进程的虚拟内存空间,每一个进程都会被分配 0~4G 的虚拟内存空间,物理内存有限,根据实际硬件决定大小;
- 3.BBS DATA 中保存的是已初始化和未初始化的全局变量/静态变量;
3.虚拟内存与物理内存的异同
- 1.物理内存可以映射到虚拟内存;
- 2.每一个进程都需要找不同的物理内存作映射;
- 3.所有进程的内核空间共享同一块物理内存,用户空间独立;
- 4.虚拟内存是由虚拟内存映射表进行管理的;虚拟内存映射表中记录了庞大繁杂的映射关系,是由内存管理器进行管理的;
- 5.虚拟内存的拟定与分配:基本单位–页(page),1页=4096Bytes(4k);三级内存间接寻址最多只能询G级单位(102410241024);
4.手动申请内存时需要与cpu进行交互,因而两次申请两页的方式效率低于一次申请两页的效率。
5.进程的状态
- 1.就绪态、运行态、挂起/睡眠态、终止态
- 2.转换关系
- 1.就绪->运行 就绪->终止 就绪->挂起
- 2.运行->就绪 运行->挂起
- 3.挂起->就绪 挂起->终止 挂起->运行
- 4.任何形态都能转化到终止态,终止态不能向其他任何状态转化
6.分时复用原则
- 1.目的:为了能够让有限的系统核心资源 CPU 被进程合理利用,使得少量的资源应用最大化,系统通过分时复用原则管理进程对 CPU 的使用权;
- 2.概念:分时复用原则就是将 CPU 的使用权 分配切片 给若干个进程,一般情况下一个时间片就能够满足当前进程的所有运行需求;
- 3.功能:时间管理、存储与恢复(用来保存进程的运行状态);
- 4.分时复用原则的存储与恢复功能是指 一个 Inter单核CPU 具有 运算器 、 控制器 、 寄存器 、 译码器 等,当一个时间片耗尽时,需要保存处理器现场,将当前处理器状态保存到 PCB 的内核栈中,当再来一个时间片给当前进程使用时,再将处理器状态恢复;
7.系统调用
- 1.系统调用是指用户在调用了某个用户层API之后,用户层API自动调用系统层API,系统层API再调用驱动层API,进而完成用户层意图完成的功能的事情;
- 2.在上述过程中,系统层API及驱动层API是用户无法调用的,但用户可以通过用户层API的调用来让系统调用下层的API来实现功能;
- 3.系统调用的实质:用户层和内核层的相互切换;
8.umask掩码
- 是Linux下创建一个新文件的权限的设置,默认是 0002;默认创建的新文件的权限是 0666 ,0666 与 0002 进行按位异或,即可得到新文件的权限。
9.ulimut 命令
- 用来限制进程对资源的使用情况的,它支持各种类型的限制;
1.2 Linux下创建进程
1.fork 函数;Linux或Unix系统进程之间具有很强的亲缘关系(父子关系),调用fork的进程是父进程,创建出来的进程是子进程;
2.系统初始化进程 init 进程称为 根进程,pid为0或1;
3.父进程创建子进程的过程
- 父进程创建子进程后,内核对子进程进行初始化(按照父进程对子进程进行复制和克隆)
- 1.首先克隆的是内核内容(非完全拷贝);
- 2.其次拷贝的是用户空间内容(完全拷贝);
- 3.子进程从 fork 行开始执行,之前的代码不执行;父进程从开头到结尾依次执行;
4.fork 函数返回值
- 1.成功时在父进程中返回子进程的pid;
- 2.在子进程中返回 0 ;
- 3.失败时在父进程中返回 -1 , error 被设置;
5.父进程的 pid 比子进程的 pid 小;
6.父子进程的变量互不影响,父进程用自己的变量,子进程也用自己的变量;
7.现在通用的 fork 函数 加入了一种 读时共享、写时复制 机制,这种机制可以规避无意义的拷贝开销,避免系统资源浪费(一开始子进程只有读权限,当子进程有写操作时父进程再给子进程拷贝一份);
1.3 僵尸进程
1.产生原因
- 子进程先于父进程结束,父进程没有回收子进程就会产生僵尸进程
2.危害
- 在子进程回收时,内核会回收用户空间资源,同时系统对子进程的内核空间绝大多数资源进行回收释放,但是仍然会有 PCB 残留, PCB 不回收会对系统造成影响,可能导致创建进程失败。
3.回收僵尸进程
- 1.kill 命令杀死僵尸进程
- 2.使用 pid_t wait(NULL) 函数,返回值为回收成功的僵尸进程的 pid
- 3.可以使用 waitpid 函数杀死僵尸进程;
- 1.函数形式: pid_t waitpid(pid_t pid, int *status, int options) ;
- 2.对于 waitpid 的第一个参数:
- pid > 0 时表示回收指定的子进程的资源;
- pid = -1 时表示回收任意子进程的资源;
- pid = 0 时表示回收当前调用进程同组的所有子进程;
- pid < -1 时表示回收指定组的所有子进程;
- 3.对于 waitpid 的第二个参数:传出子进程的终止原因,若不关心则设置为NULL;
- 4.对于 waitpid 的第三个参数:通过 options 改变 waitpid 的工作方式,例如改为非阻塞轮询回收,一般传入 WNOHANG ,意为非阻塞。
- 5.对于 waitpid 的返回值:
- 没有任何子进程则返回 -1 ;
- 没有可回收子进程返回 0 ;
- 回收成功返回回收的进程的 pid ;
4.exec族函数
- 使用 exec 族完成功能的重载,用某个程序的功能替换当前程序的功能
1.4 孤儿进程
1.父进程先于子进程终止,就会产生孤儿进程,在某些开发环境下(需要子进程自己申请空间),孤儿进程的危害远远大于僵尸进程,因为孤儿进程充满了不确定性
2.预防孤儿进程
- 开发多进程模型时,要对进程数量、进程工作状态、进程工作变化进行检测,主要检测父进程,使父进程的生命周期比大于子进程的生命周期
3.在ubuntu14.04下,所有失去父进程的子进程是由init进程托管的;在ubuntu16.04下,所有失去父进程的子进程将由图形化进程托管;将init进程kill掉会导致关机或者重启;将图形化进程kill掉会注销计算机。
4.进程间关系
- 开机运行init进程,init进程fork许多子进程,其中一个使用exec族函数运行shell(终端)程序,在该进程中再次fork子进程,用来在终端上运行用户自己生产的可执行文件
5.进程组
- Linux下所有的进程都是强亲缘关系,每一个进程都有进程组,进程组的概念非常最要,内核是通过进程组来有效管理多进程的
- 进程组组长的标志:pid == pgid
- 每个新的进程都会参加一个会话,只要会话发起者终止,就会强制杀死所有参与者,所以想要进程不受会话限制,就要脱离现有会话(脱离控制终端),让终端的存在与否与进程无关
6.守护进程(精灵进程daemon)
- 生命周期较长(随着系统持续),周期性执行某个固定任务持续在后台运行,通常脱离终端独立运行
- 守护进程是一种人为的孤儿进程
1.5 进程并发
1.概念
- 并发是指多进程共同完成指定任务可以比单进程获取CPU使用权的概率更大,及时得到更多的时间片,从而加快完成任务的速度
2.优点
- 使用多进程的并发程序,可以大大提高程序的执行效率,缩短任务完成的时间
3.串行和并行
- 串行是指 从任务的起始位置执行至末尾位置,有次序地执行
- 并行是指 依赖于硬件支持,有多少核心处理器就有多少并行数量,单核无法进行并行操作
1.6 进程间通信
1.匿名管道
- 1.通过函数 pipe 进行创建,参数为一个有两个元素的一维数组 fd , fd[0] 表示读端, fd[1] 表示写端
- 2.使用一个匿名管道:在父进程中关闭读端 fd[0] ,向 fd[1] 中写入(write)数据,关闭 fd[1] ;在子进程中关闭写端 fd[1] ,从 fd[0] 中读(read)数据,关闭 fd[0]
- 3.特性:数据流通具有方向性
- 4.优缺点
- 优点
- 简单易用
- 缺点
- 1.单工工作,只支持数据单向流通
- 2.没有名字,无法分辨
- 3.只能用于强亲缘(父子)进程间通信
- 4.pipe 函数在内核创建一个内核缓冲区,是进程共有的,是4096byte的环形队列(一页大小),通信数据大小受限制
- 5.所传送的是无格式字节流,要求管道的读端和写端必须事先约定好数据的格式
- 优点
2.有名管道
- 1.使用一个有名管道:使用 mkfifo 命令创建管道文件,创建写端,向管道中写数据;创建读端,从管道中读数据
- 2.注意:无论读或写有名管道文件,其大小都不变,为0,其中不会被写入数据,只提供内核缓冲区访问方式(读写文件描述符)
- 3.优缺点
- 优点
- 可以在任意进程间进行使用
- 缺点
- 只使用读端或只使用写端会造成堵塞
- 优点
3.mmap内存共享映射
- 1.主要通过文件完成进程间通信,利用对文件的映射与 SYNC 同步技术实现
- 2.条件:文件大小不能为 0
- 3.步骤
- 1.进程A申请映射内存,映射内存通过同步技术(SYNC)映射到映射文件中;则映射内存中的数据就会保存到映射文件中;
- 2.进程B申请映射内存,映射内存通过同步技术(SYNC)映射到映射文件中;则可能会覆盖进程A映射的映射文件,因为两者公用同一块映射文件;
- 4.mmap 和 常规文件 操作的区别
- 常规文件:常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制。这样造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。这样,通过了两次数据拷贝过程,才能完成进程对文件内容的获取任务。写操作也是一样,待写入的buffer在内核空间不能直接访问,必须要先拷贝至内核空间对应的主存,再写回磁盘中(延迟写回),也是需要两次数据拷贝
- mmap:创建新的虚拟内存区域和建立文件磁盘地址和虚拟内存区域映射这两步,没有任何文件拷贝操作。而之后访问数据时发现内存中并无数据而发起的缺页异常过程,可以通过已经建立好的映射关系,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用
- 一句话总结:常规文件操作需要从磁盘到页缓存再到用户主存的两次数据拷贝。而mmap操控文件,只需要从磁盘到用户主存的一次数据拷贝过程
- 5.mmap优缺点
- 优点
- 1.读取文件操作跨过页缓存,减少拷贝次数,用内存读写代替I/O读写,提高文件读取效率
- 2.实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而被对方空间及时捕捉
- 3.任何进程间都可以将自身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改动,达到进程间通信和进程间共享的目的
- 4.可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往是借助硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这个问题可以通过mmap映射很好的解决。换句话说,但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效
- 缺点
- 在某些情况下 read 比 mmap 效率更高
- 优点
4.消息队列(SYSTEM V 消息队列)
- 1.具有权限的任何进程都可以向消息队列中放消息,也可以读消息
- 2.常用函数
- msgget() :创建或打开一个消息队列
- msgsnd() :向消息队列中发送一条消息
- msgrcv() :从消息队列读一条消息
- msgctl() :修改消息队列属性或删除消息队列
- 3.常用属性: _MAX–消息数量; _SIZE–消息的长度; _CUR–当前消息数量
- 4.消息队列若不关闭,则会存在系统关闭;管道和mmap只持续到进程结束
5.信号
- 父进程发送信号 SIGUSR1 ,捕捉信号 SIGUSR2 ;子进程发送信号 SIGUSR2 ,捕捉信号 SIGUSR1
2.Linux下线程
2.1 Linux下线程
1.线程是最小的调度单位,线程就是寄存器和栈
2.线程和进程的区别
- 1.进程独占资源(如内存、I/O、cpu等),线程共享资源(如内存、I/O、cpu等);
- 2.进程体积远远大于线程,因此多线程程序并发性高;
- 3.进程之间的地址空间相互独立,同一进程的线程共享本进程的地址空间,因此运行效率高;
- 4.每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;
- 5.线程是处理器调度的基本单位,但是进程不是;
- 6.相同点:两者皆可并发执行;
3.线程进程的优缺点
- 1.线程执行开销小,但是不利于资源的管理和保护;线程适合在SMP机器(双CPU系统)上运行;
- 2.进程执行开销大,但是能够很好的进行资源管理和保护;进程可以跨机器前移;
4.用户级线程和内核级线程
- 1.用户线程指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程;不需要进行用户态/核心态切换,执行速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞;由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少;
- 2.内核线程指由操作系统内核创建和撤销的线程;内核维护进程及线程的上下文信息以及线程切换;一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。Windows NT和2000/XP支持内核线程;
- 3.用户线程和内核线程的区别
- 1.内核支持线程是系统内核可感知的,而用户级线程是系统内核不可感知的;
- 2.用户级线程的创建、撤消和调度不需要系统内核的支持,是在语言(如C)这一级处理的;而内核支持线程的创建、撤消和调度都需系统内核提供支持,而且与进程的创建、撤消和调度大体是相同的;
- 3.用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断;
- 4.在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由系统的线程调度程序负责线程的调度;
- 5.用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序;
- 6.内核级线程会比用户级线程得到更多的系统资源,可以得到更多的CPU使用权,从而加快任务的完成周期;
5.线程共享哪些资源
- 1.文件描述符表
- 2.信号的处理方式:sigaction
- 3.当前的工作目录
- 4.UID和GID:用户id和组id
- 5.内存的地址空间
- 6.0-3G用户空间共享的资源:
- 1.text
- 2.data
- 3.bss
- 4.堆
- 5.lib:库(包括静态库、动态库)
6.线程不共享的资源
- 1.线程ID
- 2.处理器现场和内核栈指针
- 3.用户空间栈
- 4.errorno全局变量:因为对于线程来说,errorno变量变成了局部变量,因此无法共享
- 5.阻塞信号集(屏蔽字)
- 6.进程的调度优先级(无法由线程决定)
2.2 Linux下线程创建
1.基于 NPTL 这个第三方库创建线程
2.函数
- pthread_create(pthread_t, const pthread_attr_t*, void* (start_routine)(void), void* arg);
- 参数
- 参数一:线程的ID
- 参数二:线程属性
- 参数三:线程工作函数指针
- 参数四:线程工作函数的参数,没有则为NULL
- 获取线程id函数:pthread_self(void);返回调用该函数线程的线程ID
- 回收线程的函数:pthread_join(pthread_t, void**);参数一:要回收线程的线程ID;参数二:获取线程的退出码(传出参数)
3.线程退出
- 主控线程使用 return 结束进程
- 主控线程使用 exit 终止进程
- 普通线程使用 return 终止线程并返回退出码
- 普通线程不能使用exit函数,否则就会终止当前线程所属的进程
4.线程的大小就是线程栈的大小,默认为8M
5.线程最大个数
- 1.在32位Linux系统下,该程序的结果是300+;
- 2.在64位Linux系统下,该程序的结果是10000+;因为其内存是0~4T,因此可以创建很多线程,但是因为Linux系统有限制,因此会在10000左右停止;
6.Linux下线程同步
- 1.原子操作(原子锁)
- 1.步骤:加锁->临界区(具有排他性,某个线程在处理时其他线程不能执行)->解锁
- 2.pthread_mutex_t 类型变量
- 3.可以通过更改锁的属性将 线程锁 改变为 进程锁
- 2.条件变量
- 1.基于条件来决定线程的挂起和唤醒,完成多线程协调配合,增加多线程工作的控制
- 2.pthread_cond_t 类型变量
- 3.线程同步的两个核心:互斥性(通过原子锁来完成)、协同配合性(通过条件变量来完成)
- 4.步骤:加锁->判断条件变量->临界区->唤醒其他线程->解锁
- 3.读写锁
- 1.读锁有n把,写锁只有一把
- 2.读写互斥
- 3.读共享,写独占
- 4.使用读写锁时非读即写,不能同时进行
- 4.文件锁
- 1.Linux中使用文件锁时一般可采用 lockf 或 fcntl 函数
- 2.lockf 为文件建议锁, fcntl 为可选择建议锁或强制锁
- 5.信号量
- 1.分为一元灯(锁资源)和多元灯(锁流程业务)
- 2.实现方式:初始化信号量(设置灯值,灯值为1即为一元信号灯),信号量创建出的临界区通过灯值访问,只有灯值大于0时允许进入临界区,进入后灯值减1,使用临界区代码,使用完后将灯值加1
- 3.信号量函数:无名信号量(一般使用)、有名信号量
3.死锁
3.1 概念
- 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
- 简述:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。
3.2 必要条件
- 互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。
- 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。
- 非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。
- 循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。
3.3 解决死锁的方式:
- 1.服务者模式:每次使用全局变量之前都进行询问,可以使用则使用,否则继续等待;
- 2.银行家模式:判断资源使用的风险,风险较大则拒绝使用,否则可以使用。
3.4 无锁编程与有锁编程的区别
- 速度最快的是什么都没有的编程(基础编程);其次是无锁编程;最后是有锁编程(加锁解锁会消耗大量的时间资源);
- 无锁编程无法再次进行优化,有锁编程可以进行再优化(减少锁的使用频率);
- 无锁编程简单,易于实现,但是无法再优化。