进程概念:进程/线程是计算机中最小的执行单元,是实现分时多用户操作系统的基础;那么一个进程在linux中是如何实现的呢?如果熟悉内核就知道内核就是一堆的数据结构,一个进程的所有状态就包含在task_struck这个结构中:存放进程的状态,文件/socket描述符,程序地址(stask/heap);时间片;ddl地址;权限信息等等信息;如下图:展示了进程在内存中的表示:
linux中的进程就是一个链表;进程和子进程有父子关系,进程号为1的进程为init; 进程创建子进程是运用:写时复制的原则;
线程概念: 一个进程可以包含许多线程;一个进程的各线程可以方便共享内存;线程创建时只创建一个属于本线程的栈空间;
命令: ps -ef ; ps -eLf ;pstree; pidstat;
进程的状态:running;stopped,uninterruptible; interruptitble; sleeping;zombie;
进程的销毁:进程销毁时会释放所有的数据结构(释放文件/socket句柄,内存等等),释放完后会发送一个信号给父进程,此时子进程为僵尸状态;父进程回应此信号,子进程才彻底销毁;如果父进程没有响应此信号,子进程状态会变成僵尸状态;
进程的调度: linux 是抢占式的调度策略,即正在cpu上运行的进程是随时可以被其他进程抢占的。 那么linux是如何调度进程是非常重要的;linux内核依据进程的优先级(实时优先级)和进程是属于cpu敏感还是I/O敏感型的分配不同长短的时间片; 可以说进程的调度策略是动态智能化的;进程调度的算法时间复杂度是O(1),通过为进程的不同的优先级创建对应的数组,即一个优先级对应一个数组;相同的优先级用队列存储,并有一个二进制位图来常量时间从数组中查找到将要调度的优先级队列。巧妙的用空间换取时间,最大的提高调度性能。
是指进程/线程的上下文切换(context switch) ; 上下文切换由linux内核完成将当前正在运行的进程上下文信息从寄存器中卸载下来到进程堆栈中暂存,然后Load新的进程到寄存器,高速缓存中开始执行。所以说上下文切换频繁是最影响程序的性能的因素之一。 如下图:
降低上下文切换频率是提高进程性能的关键; 对于我们的网络服务器来说,大量的客户请求随时都大量涌入服务器,cpu频繁响应网络中断会对我们的应用程序性能产生极大的影响。 如何避免频繁的上下文切换呢? 除了提高进程的优先级外; smp内核提供了亲和度这一技术,将网络中断亲和到一个CPU上去。 这样频繁的中断就不会干扰其他CPU上的应用进程的执行;
查看上下文切换频率:pidstat -w 或者 vmstat; 查看亲和度设置: cat /proc/irq/$irq/smp_affinity
linux的将内存分为kernel space 和 user space 两部分; 对于32bit 系统来说,kernel space为 1G ,user space 为3G; Linux内核的代码直接物理访问kernel space,不允许应用程序访问kernel-space;
Linux的设计考虑“安全性” 和 “多进程并发” 执行2方面考虑, 将user-space虚拟化,这样和内核空间隔离,每个进程在创建时都拥有自己的独立的虚拟进程地址空间(和其他进程地址空间互不干扰);虚拟进程空间大小拥有3G;这样每个进程看上去拥有3G的可访问内存空间一样;可以说没有内存的虚拟化,就没有并发运行的进程,也就没有多用户支持。
linux是如何分配内存的呢?: 内核是以页为单位分配内存的,页的大小一般为4kb,8kb等。 进程创建时,应用程序申请的内存(堆),都是虚拟内存地址而已,进程运行时,真正访问这块内存时,如果这块内存未曾申请过物理内存或者这块内存不在物理内存中(swap) . 进程会陷入内核态(系统调用),并产生一个page fault异常,然后申请物理内存,并和虚拟内存映射起来。
虚拟内存是如何映射到物理内存的呢?每个进程都有一张page table 维护虚拟内存到物理内存的映射关系;cpu在执行代码时先通过MMU将虚拟内存转换为物理内存,然后访问内存地址。Linux内核还通过TLB这个数据结构来对虚拟内存到物理内存的映射进行一个缓存。如下图:
命令: 查看进程虚拟内存分布:cat /proc/$pid/maps ;查看page fault的情况:pidstat -r ;
linux依靠的文件子系统屏蔽了各种不同文件系统的实现;给应用程序操作文件系统提供统一api; 并且文件系统会维护一个cache在memory中,并根据LRU算法来缓存文件内容;内核线程pdflush定期或cache占用memory占用一定百分比会被触发,将cache的内容写入disk; 如下图:
常用的文件系统有:ext3, xfs,jf (各文件系统适合不同的使用场景,表现出优越的性能)
命令:fsck,fdisk,mount -t ext3 /dev/sda /home/xxx; umount /home/xxx;iostat;sar;
linux的层次是结构如下图, 系统的硬件资源cpu/memory/io/disk等都是有内核统一管理,应用层程序要使用这些资源必须调用内核;应用层调用内核的功能是通过系统调用(system call)完成的:
那么系统调用是如何实现的呢? 当前大部分操作系统通过软中断实现。 应用程序调用内核函数时,会同时发送一个中断到cpu,cpu在中断向量表里找到此中断号对应的处理程序,从而执行内核代码。现在x86等cpu已经从硬件层开始支持系统调用这个功能,并且性能提升了不少。 因为一个程序的越少的系统调用性能越好。 通过命令strace yourexeapplication 可以看到你的可执行代码都调用了那些系统调用;
现在常用的机器架构为SMP结构(即所有CPU共享L1/L2,memory,外设);区别与NUMA结构; 所以一个某一个瞬间一个进程/线程只会在一个核上运行;一个进程频繁的在不同的CPU上运行性能会降低;因为每个核之间不共享高速缓存,如果进程频繁在核之间切换,高速缓存命中率下降,严重影响性能;操作系统内核会保证一个进程尽量运行在一个核上,并保证每个核的负载均衡。我们知道cpu要频繁的响应中断,有些中断来了,cpu必须放下正在执行的进程去响应中断,比如对于web服务器网络流量很高的服务器,网络中断很频繁;操作系统提供一个亲和度算法将一个终端号绑定到一个核上。这样其他核就不会受中断影响,提高系统性能;cat 02 >> /proc/irq/xx/smp_affinity
命令:top, uptime, vmstat; ps; pidstat;
GNU/linux的自由性来源于网络,来源于网络各端的顶尖程序员一起完成的杰作。可以说没有tcp/ip就没有linux的蓬勃发展。 linux对网络的发展也无与伦比。linux2.6内核支持同步和异步网络I/O ; 对异步的支持让现在网络服务器可以支持更多的请求量。 比如apache通过epoll,让少量的worder线程池可以响应大量的请求。如下图:
网络连接分为 : 监听连接和传输连接; 比如apache的LINSTEN连接监听在80端口上等待客户请求,客户请求过来后,监听连接会和客户端请求建立三次握手后,将连接扔给工作线程去处理此请求,此时客户端请求和worder线程建立ESTABLISHED状态,进行数据的传输。
命令:netstat,traceroute,ifconfig; dig ; hostname;