上周遇到布署在真实服务器上某个应用CPU占用过低的问题,即使经过tuning,问题其实早已解决,但我对tuning的方法只是基于大胆的假定并最终生效了。我更希望更多的求证一下程序背后CPU及OSkernel当时的运作机制。所以我读了一些Linux内核设计与实现及其他一些相关资料,对Linuxprocess的机制与切换有了更多一些感受。本文尽可能条理一点,但因为涉入点较多,同时自己可能认为个别点有记录的价值,因而文字可能会零散。
进程状态
Linux进程的状态比较容易理解,值得注意的是UNINTERRUPTIBLE及ZOMBIE
TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE此时进程不接收讯号,这就是为何有时侯kill一个忙碌的进程没有响应。
TASK_ZOMBIE我们常常kill-9pid然后运行ps会发觉被kill的进程依旧存在,状态为zombie。zombie的进程实际上早已结束,占用的资源也早已释放,仅因为kernel的相关进程描述符还未释放。
TASK_STOPPED
Kernelspaceanduserspace
Kernelspace是供内核,设备驱动运行的显存区域。userspace是供普通应用程序运行的区域。每一个进程都运行在自己的虚拟显存区域,不能访问其他进程的显存空间。普通进程不能访问kernelspace,只能通过系统调用来间接进行。当系统显存比较紧张时,非当前运行进程userspace可能会被swap到c盘。
使用命令pmap-x
可以查看进程的显存占用信息;lsof-a-p
可以查看一个进程打开的文件信息。ps-Lf
可以查看进程的线程数。
另外procfs也是一个剖析进程结构的好地方。procfs是一个虚拟的文件系统,它把系统中正在运行的进程都显现今/proc/
目录下。
进程创建
进程创建一般调用fork实现。创建后子进程和父进程指向同一显存区域,仅当子进程有write发生时侯,就会把改动的区域copy到子进程新的地址空间,这就是copy-on-write技术,它极大的提升了创建进程的速率。
Linux的线程实现
Linux线程是通过进程来实现。Linuxkernel为进程创建提供一个clone()系统调用,clone的参数包括如CLONE_VM,CLONE_FILES,CLONE_SIGHAND等。通过clone()的参数,新创建的进程,俗称为LWP(Lightweightprocess)与父进程共享显存空间,文件句柄,讯号处理等,因而达到创建线程相同的目的。
Linux2.6的线程库叫NPTL(NativePOSIXThreadLibrary)。POSIXthread(pthread)是一个编程规范,通过此规范开发的多线程程序具有良好的跨平台特点。虽然是基于进程的实现,但新版的NPTL创建线程的效率十分高。一些测试显示linux线程和进程的区别,基于NPTL的内核创建10万个线程只须要2秒linux解压命令,而没有NPTL支持的内核则须要历时15分钟。
在Linux2.6之前,Linuxkernel并没有真正的thread支持,一些threadlibrary都是在clone()基础上的一些基于userspace的封装,因而一般在讯号处理、进程调度(每位进程须要一个额外的调度线程)及多线程之间同步共享资源等方面存在一定问题。为了解决这种问题,当初IBM以前开发一套NGPT(NextGenerationPOSIXThreads),效率比LinuxThreads有显著改进,但因为NPTL的推出,NGPT也完成了相关的历史使命并停止了开发。
NPTL的实现是在kernel降低了futex(fastuserspacemutex)支持用于处理线程之间的sleep与wake。futex是一种高效的对共享资源互斥访问的算法。kernel在上面起仲裁作用,但一般都由进程自行完成。
NPTL是一个1×1的线程模型,即一个线程对于一个操作系统的调度进程,优点是十分简单。而其他一些操作系统例如Solaris则是MxN的,M对应创建的线程数嵌入式linux 培训,N对应操作系统可以运行的实体。(N
讯号
进程接收讯号有两种:同步和异步。同步讯号譬如SEGILL(非法访问),SIGSEGV(segmentationfault)等。发生这种讯号然后,系统会立刻转入内核圈套处理程序,因而同步讯号称作为圈套。异步讯号如kill,lwp_kill,sigsend等调用形成的都是,异步讯号称作为中断。
kill
调用的是SIGTERM,此讯号可以被捕获和忽视。
kill-9
调用的是SIGKILL,杀坠入程,不能被捕获和忽视。
SIGHUP是在终端被断掉时侯调用,假如讯号没有被处理,进程会中止。这就是为何忽然断网刚通过远程终端启动的进程都中止的诱因。避免的方式是在启动的命令前加上nohup命令来忽视SIGHUP讯号。如nohup./startup.sh&
好多应用程序一般捕获SIGHUP拿来实现一些自定义特点,例如通过控制台传递讯号让正在运行的程序重新加载配置文件,防止重启带来的停止服务的副作用。可惜的是,在JAVA中无法直接使用这一功能linux线程和进程的区别,SUNJVM没有官方的signal支持,虽然它早已可以实现,详情可参看SingalsandJava.
另外有个有趣的现象是zombie状态的进程kill/kill-9都没有任何作用,这是因为进程本身早已不存在,所以没有相应的进程来处理signal,zombie状态的进程只是kernel中的进程描述符及相关数据结构没有释放,但进程实体早已不存在了。