1. 进程和线程的区别
1.1 进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现操作系统的并发;线程是进程的子任务,是CPU调度和分配的基本单位,用于保证程序的实时性,实现进程内部的并发。
1.2 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。也就是说,资源分配给进程,同一进程的所有线程共享该进程的所有资源。
1.3 线程是操作系统可识别的最小执行和调度单位,每个线程独自占用一个虚拟处理器:独自的寄存器组、指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(动态内存、映射文件和目标代码)、打开的文件队列和其他内核资源。
1.4 一个线程只能属于一个进程,而一个进程可以有多个线程(至少一个)。线程依赖于进程而存在。
1.5 同一进程的多个线程共享代码段(代码和常量)、数据段(全局变量和静态变量)、扩展段(堆存储),但是每个线程拥有自己的栈段(运行时段),用于存放所有局部变量和临时变量。
1.6 创建和撤销进程时,系统要为之分配或回收资源,如内存空间、IO设备等,开销显著大于创建或撤销线程时; 进程切换时的开销也远大于线程切换,前者涉及整个当前CPU环境的保存以及被调度运行的进程的CPU环境的设置。后者只需保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。
1.7 进程编程调试简单,可靠性高,但是创建销毁开销大;线程编程调试复杂,但是开销小,切换速度快。
1.8 进程间不会相互影响,而一个线程挂掉将导致整个进程挂掉。
严格的说没有“线程崩溃”,只是触发了SIGSEGV (Segmentation Violation/Fault)。如果没有设置对应的Signal Handler操作系统就自动终止进程(或者说默认的Signal Handler就是终止进程);如果设置了,理论上可以恢复进程状态继续跑(用longjmp之类的工具)
如果该挂掉的线程改变了其他线程的区域或在共享区域造成了破坏,即使该sig信号被catch,也会导致其他就行执行异常而挂掉。
总体来说,线程没有独立的地址空间,如果崩溃,会发信号,如果没有错误处理的handler,OS一般直接杀死进程。就算是有handler了处理,一般也会导致程序崩溃,因为很有可能其他线程或者进程的数据被破坏了。
1.9 进程适用于多核、多机分布;线程适用于多核。
2. 进程间通信与线程间通信
2.1 进程间通信
2.1.1 管道
管道包括匿名管道和命名管道,前者可用于具有亲缘关系的进程间通信,后者具有前者的功能并且允许无亲缘关系进程间通信。
匿名管道PIPE
- 半双工,数据只能在一个方向上流动,单次次数据传递中具有固定的读写端(下一次可能就互换了)
- 父子进程或者兄弟进程间通信
- 可视为特殊的文件,对于读写可以使用read/write函数,但是其不属于任何文件系统,只存在与内存中
命名管道FIFP
- 无关进程间通信
- 有路径名与之相关联,以一种特殊设备文件形式存在于文件系统中
2.1.2 系统IPC
消息队列
消息的链接表,存放在内核中,由一个标识符(队列ID)来标记,克服了信号传递信息少和管道只能承载无格式字节流以及缓冲区大小受限的缺点。
具有写权限的进程可以按照一定规则向消息队列中添加新信息,具有读权限的进程可以从消息队列中读取信息。
有三个特点:
- 消息队列面向记录,其中的消息具有特定的格式以及特定的优先级
- 消息独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除
- 消息队列可以实现消息的随机查询(不一定先进先出),也可按照消息的类型读取
信号量semaphore
一个计数器,用于控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而非存储进程间通信数据。
有四个特点:
- 信号量用于进程间同步,若要传递数据需要结合共享内存
- 信号量基于操作系统的PV操作,对信号量的操作都是原子操作
- 每次的PV操作不仅可加1减1,可以加减任意正整数
- 信号量组
信号signal
用于通知接收进程某个事件已经发生。
共享内存
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新,需要依靠某种同步操作,比如互斥锁与信号量。
三个特点:
- 最快的IPC,因为进程直接对内存进行读取
- 需要同步
- 常与信号量一起使用(信号量完成同步)
2.1.3 套接字SOCKET
可以在本机进程间,也可以不同主机间的进程间通信。
2.2 线程间
临界区 通过多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
互斥量Synchronized/Lock 采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。
信号量Semphare 为控制具有有限数量的用于资源而设计,它允许多个线程在同一时刻去访问同一个资源,但需要限制同一时刻访问此资源的最大线程数目。
信号Wait/Notify 通过通知操作的方式来保持多线程同步,还可以方便地实现多线程优先级的比较操作。
3. 虚拟内存
目的是为了防止不同进程同一时刻在物理内存中运行而对物理内存进行争夺和践踏。虚拟内存为每个进程都营造了一种运行时独自占有当前系统内存的假象。
事实上,每个进程创建加载时,内核只是为进程创建了虚拟内存的布局(即初始化进程控制表中内存相关的链表),实际不立即把虚拟内存对应位置的程序数据和代码拷贝到物理内存中,只是建立好虚拟内存和磁盘文件的映射。等运行到对应程序时,才会通过缺页异常来拷贝数据。动态内存分配比如malloc也只是分配了虚拟内存,即为这块虚拟内存对应的页表项进行设置,等进程真正访问到此数据时,才引发缺页异常。
使用虚拟内存的好处在于:
- 扩大地址空间
- 进程运行在各自的虚拟内存地址空间,互不干扰;并且虚存还对特定的内存地址提供保护,可以防止代码或数据被恶意篡改
- 公平内存分配,每个进程都有同样大小的虚存空间
- 进程通信可采用虚存共享的方式实现
- 当不同的进程使用同样的代码时(比如库文件中的代码),物理内存可以只存储一份这样的代码,不同的进程只需要把自己的虚拟内存映射过去以节省内存
- 适用于多道程序设计系统,许多程序的片段同时保存在内存中。当一个程序等待它的一部分读入内存时,可以把CPU交给另一个进程使用,提高系统并发度
- 程序需要分配连续的内存空间时,只需要在虚拟内存空间分配连续空间而不需要实际物理内存的连续空间,可以利用碎片
使用虚拟内存的代价有:
- 虚存的管理需要建立额外的数据结构,占据内存
- 虚拟地址到物理地址的转换,增加了指令的执行时间
- 页面的换入换出需要磁盘IO,耗时
- 如果一页中只有一部分数据,会浪费内存
4. 缺页中断
malloc和mmap等内存分配函数分配时知识建立了进程的虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。
在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在与内存中,若不在,就触发一次缺页中断,此时操作系统根据页表中的外存地址在外存中找到所缺的一页将其调入内存。
缺页中断和其他中断一样,需要四个步骤:
- 保护CPU现场
- 分析中断原因
- 转入缺页中断处理程序进行处理
- 恢复CPU现场,继续执行
但缺页中断也有独特之处,它是由硬件产生:
- 在指令执行期间产生和处理缺页中断信号
- 一条指令在执行期间可能产生多次缺页中断
- 缺页中断返回时,执行产生中断的一条指令(一般中断返回时执行下一条指令)
5. fork()和vfork()
5.1 fork()
创建一个和当前进程一样的进程,两个进程都会继续运行。在子进程中,成功的fork()调用返回0,父进程中成功的fork()调用返回pid。失败则返回负值。
常见用法是创建一个新的进程然后使用exec()载入二进制映像进行替换,这样纠缠色很难过了新的进程,即派生+执行。
int main(void)
{
pid_t pid;
signal(SIGCHLD, SIG_IGN);
printf("before fork pid:%d\n", getpid());
int abc = 10;
pid = fork();
// if-else if-else对应pid<0,>0,=0三种情况
现代Unix系统在调用fork()时不再把所有内部数据结构复制一份然后逐页复制到子进程的地址空间,而是采用了写时复制的方法。
写时复制
惰性优化,若多个进程要读取它们自己的那部分资源的副本时,每个进程保存一个指向该资源的指针即可,不用复制。如果某一进城要修改那份资源时,才复制给该进程。
在使用虚拟内存的情况下,写时复制以页为基础。在内核中,与内核页相关的数据结构标记为只读和写时复制,如果由进程试图修改一个页,就会产生一个缺页中断。内核处理缺页中断的方式就是对该页进行一次透明复制(过程中清除COW属性,表示不再被共享)
5.2 vfork()
vfork()调用,会挂起父进程直到子进程终止或者运行了一个新的可执行文件的映像,以避免地址空间的按页复制,但是fork()+写时复制虽然没有vfork()快,但是也足够用了,现在vfork基本都是封装过的fork()。
5.3 fork() v.s. vfork()
- fork()的子进程拷贝父进程的数据段和代码段,vfork()的子进程与父进程共享数据段
- fork()的父子进程的执行次序不确定;vfork()保证子进程先运行,在调用exec或exit前与父进程的数据是共享的,调用之后父进程才可能被调度运行。如果调用之前子进程依赖父进程的进一步动作,会导致死锁
- vfork()当需要改变共享数据中变量的值,则拷贝父进程。
6. 修改文件最大句柄数
Linux默认最大文件句柄数是1024。当文件并发量较大时会报“too many open files”,修改文件最大句柄数的方法:
ulimit -n 2048,但是只对当前进程有效vi /etc/security/limits.conf添加:
*soft nofile 65536
*hard nofile 65536
然后保存、注销、重新登陆。
7. 并行和并发
并行:严格物理意义上的同时运行,比如两个程序分别运行在两

最低0.47元/天 解锁文章
832

被折叠的 条评论
为什么被折叠?



