服务器学习心得


进程管理

<ul> <li><a href="https://www.topgoer.cn/docs/data-structures-questions/data-structures-questions-1d94t1bk0bgcl">https://www.topgoer.cn/docs/data-structures-questions/data-structures-questions-1d94t1bk0bgcl</a></li> <li><a href="https://www.cnblogs.com/valjeanshaw/p/11469514.html">https://www.cnblogs.com/valjeanshaw/p/11469514.html</a></li> <li><a href="https://www.zhihu.com/question/410231741">https://www.zhihu.com/question/410231741</a></li> <li><a href="https://zhuanlan.zhihu.com/p/52845869">https://zhuanlan.zhihu.com/p/52845869</a></li> </ul> <h2>进程</h2> <ul> <li>程序一次进行(执行)的过程,是程序执行过程中分配、管理资源基本单位。</li> </ul> <h2>线程</h2> <ul> <li>CPU调度和分派的基本单位。</li> <li>一个进程中可以有多个线程,它们共享进程资源。</li> </ul> <h2>进程和线程根本区别</h2> <ul> <li>进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位</li> </ul> <h2>进程和线程区别</h2> <ul> <li>拥有资源 <ul> <li>进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。</li> </ul></li> <li>调度 <ul> <li>线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程中的线程时,会引起进程切换。</li> </ul></li> <li>系统开销 <ul> <li>由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。</li> <li>类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。</li> </ul></li> <li>通信方面 <ul> <li>进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。</li> </ul></li> </ul> <h2>进程、线程、多核CPU,内存,硬盘关系</h2> <ul> <li>系统为进程分配资源(内存),CPU调度、执行线程</li> <li>多核CPU,进程中多线程并行执行。单核CPU,进程中多线程并发执行,根据时间片切换线程</li> <li>同一个线程同一时间段只能在一个cpu内核中运行,线程是进程一部分</li> <li>一个进程中多个线程,共享进程资源</li> <li>CPU控制器(发布指令),运算器(处理数据)</li> <li>内存临时存储数据,主要连接硬盘和CPU,是执行者,根据指令工作</li> <li>硬盘保存数据,如常用的IO(输入/输出)操作</li> </ul> <h2>进程、线程、协程关系(linux中)</h2> <ul> <li>进程有自己独立的内存空间,线程是共享内存空间</li> <li>进程切换时需要转换内存地址空间,线程切换没有这个动作,线程切换比进程切换代价更小 <ul> <li>线程降低进程切换过程中的开销</li> <li>线程占用内存是 MB 级别</li> </ul></li> <li>协程是用户态,是一个数据结构,不用系统调用,内存为 KB 级别,减少上下文切换次数</li> </ul> <h2>进程状态的切换</h2> <ul> <li>就绪状态(ready):等待被调度</li> <li>运行状态(running)</li> <li>阻塞状态(waiting):等待资源</li> </ul> <h2>进程调度算法</h2> <h5>批处理系统</h5> <p>没有太多的用户操作,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。</p> <ul> <li>先来先服务 <ul> <li>按照请求的顺序进行调度</li> </ul></li> <li>短作业优先 <ul> <li>按估计运行时间最短的顺序进行调度</li> </ul></li> <li>最短剩余时间优先 <ul> <li>按估计剩余时间最短的顺序进行调度</li> </ul></li> </ul> <h5>交互式系统</h5> <p>有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。</p> <ul> <li> <p>时间片轮转算法</p> <ul> <li> <p>将所有进程按FCFS (先来先服务) 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。</p> </li> <li> <p>当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。</p> </li> <li> <p>时间片轮转算法的效率和时间片的大小有很大关系。</p> </li> <li>因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。</li> </ul> </li> <li> <p>优先级调度</p> <ul> <li> <p>为每个进程分配一个优先级,按优先级进行调度。</p> </li> <li>为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。</li> </ul> </li> <li> <p>多级反馈队列</p> <ul> <li> <p>通常,如果一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。</p> </li> <li> <p>多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。</p> </li> <li> <p>每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。</p> </li> <li>可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。</li> </ul> </li> </ul> <h5>实时系统</h5> <ul> <li>要求一个请求在一个确定时间内得到响应。</li> <li>分为硬实时和软实时 <ul> <li>硬实时必须满足绝对的截止时间,</li> <li>软实时可以容忍一定的超时。</li> </ul></li> </ul> <h2>进程同步</h2> <ul> <li> <p>临界区</p> <ul> <li>对临界资源进行访问的那段代码称为临界区。</li> <li>为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。</li> </ul> </li> <li> <p>同步与互斥</p> <ul> <li> <p>同步:多个进程按一定顺序执行;</p> </li> <li>互斥:多个进程在同一时刻只有一个进程能进入临界区</li> </ul> </li> <li> <p>信号量(Semaphore)</p> <p>是一个整型变量,执行 down 和 up 操作,也就是常见的 P 和 V 操作。</p> <blockquote> <p>P和 V 是来源于两个荷兰语词汇,</p> <p>P() ---prolaag (荷兰语,尝试减少的意思)</p> <p>V() ---verhoog(荷兰语,增加的意思)</p> </blockquote> <ul> <li>down (减少)</li> <li>如果信号量大于 0 ,执行 -1 操作;</li> <li>如果信号量等于 0,进程睡眠,等待信号量大于 0;(阻塞)</li> <li>up(增加)</li> <li>对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。(唤醒)</li> </ul> </li> </ul> <p>​ down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。</p> <p>​ 如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。</p> <ul> <li> <p>管程((Monitors)</p> <ul> <li> <p>也称为监视器,是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。</p> </li> <li> <p>使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。</p> </li> <li>管程是为了解决信号量在临界区的 PV 操作上的配对的麻烦,把配对的 PV 操作集中在一起,生成的一种并发编程方法。其中使用了条件变量这种同步机制。</li> <li>管程重要特性</li> <li>在一个时刻只能有一个进程使用管程。</li> <li>进程在无法继续执行的时候不能一直占用管程,否者其它进程永远不能使用管程。</li> </ul> </li> </ul> <h2>使用信号量实现生产者-消费者问题</h2> <p>通常,使用一个缓冲区来保存数据,只有缓冲区没有满,生产者才可以放入数据;只有缓冲区不为空,消费者才可以拿走数据。</p> <p>因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。</p> <p>为了同步生产者和消费者的行为,需要记录缓冲区中数据的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入数据;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走数据。</p> <p>这里需要注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,也就无法执行 up(empty) 操作,empty 永远都为 0,那么生产者和消费者就会一直等待下去,造成死锁。</p> <h2>进程同步与进程通信</h2> <p>让进程同步,需要进程进行通信,传输一些进程同步所需要的信息</p> <ul> <li>进程同步 <ul> <li>控制多个进程按一定顺序执行</li> </ul></li> <li>进程通信 <ul> <li>进程间传输信息</li> </ul></li> </ul> <h2>线程间通信</h2> <ul> <li>同步(synchronized) <ul> <li>同步本质上就是 “共享内存” 式的通信。</li> <li>多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。</li> </ul></li> <li>轮询(while) <ul> <li>不断地改变条件,检测条件是否成立 ,实现线程间的通信。浪费 CPU 资源。</li> </ul></li> <li>异步(notify)</li> </ul> <h2>进程间通信</h2> <ul> <li>管道(Pipe) <ul> <li>管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。</li> </ul></li> <li>命名管道(named pipe) <ul> <li>命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关 系 进程间的通信。</li> <li>命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。</li> </ul></li> <li>信号(Signal) <ul> <li>信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身;</li> <li>Linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。</li> </ul></li> <li>消息(Message)队列 <ul> <li>消息队列是消息的链接表,包括Posix消息队列system V消息队列。</li> <li>有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。</li> <li>消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺</li> </ul></li> <li>共享内存 <ul> <li>使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。</li> <li>是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。</li> </ul></li> <li>内存映射(mapped memory) <ul> <li>内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。</li> </ul></li> <li>信号量(semaphore) <ul> <li>主要作为进程间以及同一进程不同线程之间的同步手段。</li> </ul></li> <li>套接口(Socket) <ul> <li>更为一般的进程间通信机制,可用于不同机器之间的进程间通信。</li> <li>起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:linux和System V的变种都支持套接字。</li> </ul></li> </ul> <h2>一般程序转换为进程几个步骤</h2> <ol> <li>内核将程序读入内存,为程序分配内存空间.</li> <li>内核为该进程分配进程标识符 PID 和其他所需资源.</li> <li>内核为进程保存 PID 及相应的状态信息,把进程放到运行队列中等待执行,程序转化为进程后可以被操作系统的调度程序调度执行了.</li> </ol> <h2>父子进程的共享资源</h2> <ul> <li>子进程完全复制了父进程的地址空间的内容,包括堆栈段和数据段的内容。</li> <li>子进程并没有复制代码段,而是和父进程共用代码段。 <ul> <li>子进程可能执行不同的流程,会改变数据段和堆栈段,需要分开存储父子进程各自的数据段和堆栈段。</li> <li>但是代码段是只读的,不存在被修改的问题,因此这一个段可以让父子进程共享,以节省存储空间.</li> </ul></li> </ul> <h2>创建共享空间的子进程</h2> <ul> <li>进程在创建一个新的子进程之后,子进程的地址空间完全和父进程分开。</li> <li>父子进程是两个独立的进程,接受系统调度和分配系统资源的机会均等,因此父进程和子进程更像是一对兄弟。如果父子进程共用父进程的地址空间,则子进程就不是独立于父进程的。</li> </ul> <h2>孤儿进程和僵尸进程</h2> <p>在 Unix/Linux 中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。</p> <p>子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。</p> <ul> <li>孤儿进程 <ul> <li>一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。</li> <li>孤儿进程将被 init 进程(进程号为1)所收养,并由 init 进程对它们完成状态收集工作.</li> </ul></li> <li>僵尸进程 <ul> <li>一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中</li> </ul></li> </ul> <h3>守护进程</h3> <ul> <li>运行在后台的一种特殊进程。</li> <li>它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。</li> <li>它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。</li> <li>Linux系统的大多数服务器就是通过守护进程实现的。 <ul> <li>常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。</li> </ul></li> </ul> <h2>上下文切换</h2> <ul> <li>上下文切换,有时也称做进程切换或任务切换,是指CPU从一个进程或线程切换到另一个进程或线程。</li> <li>在操作系统中,CPU 切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:</li> <li>当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务.</li> <li>根据任务的不同,可以分为进程上下文切换,线程上下文切换,中断上下文切换</li> </ul> <h2>CPU上下文</h2> <ul> <li>CPU上下文:CPU寄存器和程序计数器是CPU运行任何任务前,必须依赖的环境</li> <li>CPU寄存器 <ul> <li>是CPU内置的容量小、但速度快的内存,用来临时存放指令执行运行过程中的操作数和中间(最终)的操作结果。</li> </ul></li> <li>程序计数器 <ul> <li>用来存储CPU正在运行的指令位置、或者即将执行的下一条指令位置。</li> </ul></li> </ul> <h2>造成死锁主要原因</h2> <ol> <li>可重用资源引起的</li> <li>消耗资源过多引起的</li> </ol> <h2>死锁</h2> <ul> <li>多个线程或进程对同一个资源的争抢或相互依赖</li> </ul> <h2>死锁处理策略</h2> <ul> <li>鸵鸟策略 <ul> <li>把头埋在沙子里,假装根本没发生问题</li> <li>大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。检测死锁并且恢复</li> </ul></li> </ul>

页面列表

ITEM_HTML