了解Linux的进程与线程

了解Linux的进程与线程

 


https://blog.csdn.net/ce123_zhouwei/article/details/118633387
通过Linux的top和ps命令中,默认看到最多的是pid (process ID),也许你也能看到lwp (thread ID)和tgid (thread group ID for the thread group leader)等等,而在Linux库函数和系统调用里也许你注意到了pthread id和tid等等。还有更多的ID,比如pgrp (process group ID), sid (session ID for the session leader)和 tpgid (tty process group ID for the process group leader)。
Linux下的各种ID:

pid:进程ID。
lwp:线程ID。在用户态的命令(比如ps)中常用的显示方式。
tid:线程ID,等于lwp。tid在系统提供的接口函数中更常用,比如syscall(SYS_gettid)和syscall(__NR_gettid)。
tgid:线程组ID,也就是线程组leader的进程ID,等于pid。
pgid:进程组ID,也就是进程组leader的进程ID。
pthread id:pthread库(Linux 2.6的线程库(Native POSIX Thread Library)。POSIX thread(pthread)是一个编程规范)提供的ID,生效范围不在系统级别,可以忽略。
sid: session ID for the session leader。
tpgid: tty process group ID for the process group leader。

关系
lwp==tid
tgid==pid

 


 

http://timyang.net/linux/linux-process/

 

上周碰到部署在真实服务器上某个应用CPU占用过高的问题,虽然经过tuning, 问题貌似已经解决,但我对tuning的方式只是基于大胆的假设并最终生效了。我更希望更多的求证一下程序背后CPU及OS kernel当时的运作机制。所以我读了一些Linux内核设计与实现及其他一些相关资料,对Linux process的机制与切换有了更多一些体会。本文尽可能条理一点,但由于牵涉点较多,同时自己可能觉得某些点有记录的价值,因此文字可能会零散。

  • 进程状态

Linux进程的状态比较容易理解,值得注意的是 UNINTERRUPTIBLE 及 ZOMBIE

TASK_RUNNING
TASK_INTERRUPTIBLE
TASK_UNINTERRUPTIBLE 此时进程不接收信号,这就是为什么有时候kill一个繁忙的进程没有响应。
TASK_ZOMBIE 我们经常 kill -9 pid 之后运行ps会发现被kill的进程仍然存在,状态为 zombie。zombie的进程实际上已经结束,占用的资源也已经释放,仅由于kernel的相关进程描述符还未释放。
TASK_STOPPED

  • Kernel space and user space

Kernel space是供内核,设备驱动运行的内存区域。user space是供普通应用程序运行的区域。每一个进程都运行在自己的虚拟内存区域,不能访问其他进程的内存空间。普通进程不能访问kernel space, 只能通过系统调用来间接进行。当系统内存比较紧张时,非当前运行进程user space可能会被swap到磁盘。

使用命令 pmap -x <pid> 可以查看进程的内存占用信息; lsof -a -p <pid> 可以查看一个进程打开的文件信息。ps -Lf <pid> 可以查看进程的线程数。
ps- L 列出栏位的相关信息
ps -f    显示UID,PPIP,C与STIME栏位


lsof -a 表示两个参数都必须满足时才显示结果
lsof -p  <pid>  查看指定进程打开的文件

root@mongo_mong_17 tmp]# ps -Lf  3282 
UID        PID  PPID   LWP线程ID  C NLWP STIME TTY      STAT   TIME CMD
mongodb   3282     1  3282  0   58  2021 ?        SLl    0:00 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3284  0   58  2021 ?        SLl    0:00 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3285  0   58  2021 ?        SLl   87:06 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3287  0   58  2021 ?        SLl   92:27 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3288  0   58  2021 ?        SLl    9:02 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3289  0   58  2021 ?        SLl   33:53 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3290  0   58  2021 ?        SLl   14:35 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3291  0   58  2021 ?        SLl   14:38 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3292  0   58  2021 ?        SLl   14:39 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3293  0   58  2021 ?        SLl   14:32 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3294  0   58  2021 ?        SLl    6:21 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3295  0   58  2021 ?        SLl    1:32 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3296  0   58  2021 ?        SLl  153:21 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf
mongodb   3282     1  3297  0   58  2021 ?        SLl   53:19 /usr/local/mongodb/bin/mongod -f /data/mongodb/mongodb3717/mongod.conf

 htop pid那一列 既显示pid又显示tid

 mongodb4.0不是用pthread编程规范,下面是mongodb日志

2022-09-10T01:27:04.412+0800 I -        [listener] pthread_create failed: Resource temporarily unavailable
2022-09-10T01:27:04.412+0800 W EXECUTOR [conn68683] Terminating session due to error: InternalError: failed to create service entry worker thread

 

 

另外procfs也是一个分析进程结构的好地方。procfs是一个虚拟的文件系统,它把系统中正在运行的进程都显现在/proc/<pid>目录下。

  • 进程创建

进程创建通常调用fork实现。创建后子进程和父进程指向同一内存区域,仅当子进程有write发生时候,才会把改动的区域copy到子进程新的地址空间,这就是copy-on-write技术,它极大的提高了创建进程的速度。

  • Linux的线程实现

Linux线程是通过进程来实现。Linux kernel为进程创建提供一个clone()系统调用,clone的参数包括如 CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。通过clone()的参数,新创建的进程,也称为LWP(Lightweight process)与父进程共享内存空间,文件描述符,信号处理等,从而达到创建线程相同的目的。lwp:线程ID

Linux 2.6的线程库叫NPTL(Native POSIX Thread Library)。POSIX thread(pthread)是一个编程规范,通过此规范开发的多线程程序具有良好的跨平台特性。尽管是基于进程的实现,但新版的NPTL创建线程的效率非常高。一些测试显示,基于NPTL的内核创建10万个线程只需要2秒,而没有NPTL支持的内核则需要长达15分钟。(Linux就这个范儿P518)  用轻量进程模拟线程

在Linux 2.6之前,Linux kernel并没有真正的thread支持(pgsql用多进程),一些thread library都是在clone()基础上的一些基于user space的封装,因此通常在信号处理、进程调度(每个进程需要一个额外的调度线程)及多线程之间同步共享资源等方面存在一定问题。为了解决这些问题,当年IBM曾经开发一套NGPT(Next Generation POSIX Threads), 效率比 LinuxThreads有明显改进,但由于NPTL的推出,NGPT也完成了相关的历史使命并停止了开发。

NPTL的实现是在kernel增加了futex(fast userspace mutex)支持用于处理线程之间的sleep与wake。futex是一种高效的对共享资源互斥访问的算法。kernel在里面起仲裁作用,但通常都由进程自行完成。

NPTL是一个1×1的线程模型,即一个线程对应一个操作系统的调度进程,优点是非常简单。而其他一些操作系统比如Solaris则是MxN的,M对应创建的线程数,N对应操作系统可以运行的实体。(N<M),优点是线程切换快,但实现稍复杂。

  • 信号

进程接收信号有两种:同步和异步。同步信号比如SIGILL(非法访问), SIGSEGV(segmentation fault)等。发生此类信号之后,系统会立即转到内核陷阱处理程序trap命令,因此同步信号也称为陷阱异步信号如kill, lwp_kill, sigsend等命令产生的都是,异步信号也称为中断。

kill <pid> 调用的是 SIGTERM,编号为15, 此信号可以被捕获和忽略。

kill -9 <pid> 调用的是 SIGKILL,编号为9, 杀掉进程,不能被捕获和忽略。

SIGHUP是在终端被断开时候调用,如果信号没有被处理,进程会终止。这就是为什么突然断网刚通过远程终端启动的进程都终止的原因。防止的方法是在启动的命令前加上 nohup 命令来忽略 SIGHUP信号。如  nohup ./startup.sh & 

很多应用程序(例如nginx)通常捕获SIGHUP用来实现一些自定义特性,比如通过控制台传递信号让正在运行的程序重新加载配置文件,避免重启带来的停止服务的副作用。可惜的是,在JAVA中没法直接使用这一功能,SUN JVM没有官方的signal支持,尽管它已经可以实现,详情可参看Singals and Java.

另外有个有趣的现象是 zombie 状态的进程 kill/kill -9 都没有任何作用,这是由于进程本身已经不存在,所以没有相应的进程来处理signal, zombie状态的进程只是kernel中的进程描述符及相关数据结构没有释放,但进程实体已经不存在了。

关于僵尸进程,也可参看下酷壳上的这篇Linux 的僵尸(zombie)进程,从程序的角度解释了相关原理。

如想及时阅读Tim Yang的文章,可通过页面右上方扫码订阅最新更新。

« 高性能网站经验-读杨建的Blog有感 | QCon Beijing qconbeijing全部演讲资料下载 »

9 Comments  »

    1. Practicing all over again exercises on the subject of day to day lows will be exceptionally sensible plus states big remedy while in the immediate plus new.

       
    2. 027xiatian

      SIGHUP是在终端被断开时候调用,如果信号没有被处理,进程会终止。这就是为什么突然断网刚通过远程终端启动的进程都终止的原因。防止的方法是在启动的命令前加上 nohup 命令来忽略 SIGHUP信号。如 nohup ./startup.sh &

      是笔误么?
      终端有前台进程组和后台进程组,默认情况下,终端接收到任何信号都会发到前台进程组, 控制终端对SIGHUP信号没有做特别处理,默认就会退出, nohup实际是创建一个脱离控制终端的守护进程【控制终端产生的SIGHUP它是收不到的】,

      应该是这样的吧

       
    3. 总结的还是不错的

       
    4. linhuatan

      Linux 的僵尸(zombie)进程链接坏了

       

posted @ 2015-01-19 16:06  xiaohuazi  阅读(1460)  评论(0编辑  收藏  举报