Menu Close

Linux 进程的详细解释

Linux 是一个以稳定性和灵活性著称的开源操作系统,其高效的进程管理至关重要。对于刚接触 Linux 的初学者来说,理解进程并掌握相关命令对于有效的系统管理和故障排除至关重要。本指南将全面探讨 Linux 进程、它们的管理方式,以及如 ps、top、jobs bg 等关键命令,专为 Linux 生态系统的新手量身定制。

一)什么是 Linux 进程?

Linux 操作系统中,进程(Process) 被定义为正在执行的程序。它表示一个正在运行的程序实例,包括程序代码和当前的活动状态。Linux 为每个进程分配一个唯一的 进程 ID(PID),使操作系统能够有效地管理和跟踪进程。在 Linux 中,进程(Process) 是正在运行的程序实例,包括程序代码、数据、资源等。

Linux 是一个多任务操作系统,可以同时运行多个进程,并通过进程调度和管理保证系统的稳定运行。

  • 进程是一个 程序在运行中的实例,是资源分配的基本单位。
  • 进程可以拥有多个 线程,线程是调度的基本单位。
  • Linux 使用 PID(进程ID) 来唯一标识一个进程

1.1) 进程的组成

一个进程由以下几个部分组成:

  • 代码段(Text Section):存放可执行代码。
  • 数据段(Data Section):存放全局变量和静态变量。
  • 堆(Heap):动态分配的内存区域(malloc/new)。
  • 栈(Stack):存放函数调用信息、局部变量等。

1.2) 理解进程状态

在其生命周期中,Linux 进程可以经历多个状态转换:

  • 运行(Running)进程当前正在 CPU 上执行。
  • 睡眠(Sleeping)进程正在等待某个事件发生,例如 I/O 操作完成。
  • 停止(Stopped)进程已被暂停,通常是由于接收到某个信号。
  • 僵尸(Zombie)进程已执行完成,但其父进程尚未读取其退出状态。

理解这些状态对于高效管理进程和排查系统问题至关重要。

1.3) 调度信息(Scheduling Information)

调度程序需要这些信息,以便公平地决定系统中哪个进程最应该运行。

1.4) 标识符(Identifiers)

  • 系统中的每个进程都有一个唯一的 进程标识符(PID)
  • 进程标识符 不是 任务向量(task vector)中的索引,而仅仅是一个编号。
  • 每个进程还具有 用户标识符(User ID, UID)组标识符(Group ID, GID),它们用于控制进程对系统中文件和设备的访问权限。

      1.5) 时间和定时器(Times and Timers)

      • 内核会跟踪 进程的创建时间以及其整个生命周期内消耗的 CPU 时间。
      • 每个 时钟滴答(clock tick),内核会更新进程系统模式(system mode)用户模式(user mode) 下消耗的时间(以 jiffies 计)。
      • Linux 还支持进程特定的间隔定时器(Interval Timers),进程可以使用 系统调用(system calls) 来设置定时器,以便在到期时向自身发送信号。
      • 这些定时器可以是一次性(single-shot) 也可以是周期性(periodic) 的。

      1.6) 文件系统(File System)

      • 进程可以随意打开和关闭文件,每个进程task_struct 结构体都包含指向以下内容的指针:
        • 已打开文件的文件描述符(File Descriptors)。
        • 两个 VFS(虚拟文件系统)inode 指针
          • 根目录(Root Directory):即进程主目录
          • 当前工作目录(Current Working Directory,pwd):来源于 Unix 命令 pwd(打印工作目录)。
      • VFS inode 唯一标识文件系统中的一个文件或目录,并提供与底层文件系统的统一接口。
      • 这些 VFS inode 的 引用计数(count 字段) 会增加,表示它们被一个或多个进程引用:
        • 这也是为什么 无法删除 进程当前所在的 pwd 目录 或其 子目录,因为它们仍被进程占用。

      1.7) 虚拟内存(Virtual Memory)

      • 大多数进程都有虚拟内存(内核线程和守护进程 daemon 除外)。
      • Linux 内核必须跟踪 这些虚拟内存是如何映射到 物理内存 的,以确保高效的内存管理。

      1.8) 处理器特定上下文(Processor Specific Context)

      • 进程可以被看作是当前系统状态的总和
      • 进程运行时,它会使用 CPU 寄存器、栈(stack)等资源,这些构成了进程上下文(context)
      • 进程挂起(suspended) 时,所有 CPU 相关的上下文都会被保存在 task_struct 结构体中
      • 进程调度器(scheduler) 重新启动时,它的上下文会从 task_struct 中恢复,以确保进程能够继续执行。

      1.9)守护进程(Daemons)

      这些是特殊类型的后台进程,在系统启动时自动运行,并作为服务持续运行,不会终止。它们作为系统任务(以服务的形式)自发启动。然而,用户可以通过 init 进程对其进行控制。

      守护进程的主要特点:

      1. 后台运行

        • 守护进程在后台运行,不需要用户直接交互。
      2. 随系统启动

        • 这些进程通常在系统启动时自动加载并运行。
      3. 服务型进程

        • 它们提供关键的系统功能,例如日志记录、网络管理、任务调度和设备管理。
      4. 无控制终端

        • 与普通进程不同,守护进程与终端会话分离,不会直接与用户交互。
      5. 由 Init/Systemd 管理

        • 守护进程通常由init(如 SysVinit、Upstart)或systemd(现代 Linux 系统)进行管理。
      READ  Linux系统信息与管理命令清单

      常见的守护进程示例:

      守护进程名称 功能
      cron 计划和执行自动化任务(定时任务)。
      sshd 处理 SSH 远程登录会话。
      httpd / nginx Web 服务器守护进程
      mysqld 管理 MySQL 数据库服务。
      systemd-journald 处理系统日志记录。

      在 Systemd(现代 Linux 系统)中管理守护进程

      大多数现代 Linux 发行版使用 systemd 来管理守护进程,可以使用以下命令控制它们:

      • 启动守护进程sudo systemctl start <daemon_name>
      • 停止守护进程sudo systemctl stop <daemon_name>
      • 设置开机自启sudo systemctl enable <daemon_name>
      • 检查守护进程状态sudo systemctl status <daemon_name>

      守护进程Linux 系统的正常运行中起着至关重要的作用。它们在后台静默运行,负责网络、调度、日志等关键服务,确保系统稳定高效地运作。

      二)Linux 进程的类型

      Linux 进程可以大致分为两大类:

      1. 前台进程(Foreground Processes)

        • 这些是交互式进程,需要用户输入,并在前台执行。
        • 它们直接与终端关联,并可使用作业控制命令进行管理。
        • 前台进程通常会占用终端,直到执行完成或被手动挂起。
      2. 后台进程(Background Processes)

        • 这些进程独立于用户交互运行,通常用于系统服务和长时间执行的任务。
        • 可以通过在命令末尾添加 & 符号或使用 nohup 命令来启动后台进程,以确保即使用户注销后进程仍然继续运行。

      三)Linux 进程的生命周期

      linux process 进程
      linux process 进程

      一个进程Linux 中经历以下几个阶段:

      1. 创建(Create)进程通过 fork() 产生一个子进程
      2. 就绪(Ready)进程等待 CPU 调度执行。
      3. 运行(Running)进程CPU 上执行。
      4. 阻塞(Blocked)进程等待某个事件(如 I/O)。
      5. 终止(Terminated)进程执行完毕或被终止,释放资源。

      四)进程创建

      通常,当一个现有进程在内存中创建一个与自身完全相同的副本时,会生成一个新进程。子进程的环境与父进程相同,唯一不同的是进程 ID 号。

      Linux 中,有两种常见的方法用于创建新进程

      1. 使用 system() 函数 – 这种方法相对简单,但效率较低,并且存在一定的安全风险。
      2. 使用 fork()exec() 函数 – 这种技术稍微复杂一些,但提供了更大的灵活性、更高的速度以及更好的安全性。

      Linux 主要通过 fork() 创建新进程

      #include <unistd.h>
      #include <stdio.h>
      
      int main() {
          pid_t pid = fork();  // 创建子进程
          if (pid == 0) {
              printf("子进程:PID=%d, 父进程 PID=%d\n", getpid(), getppid());
          } else {
              printf("父进程:PID=%d, 子进程 PID=%d\n", getpid(), pid);
          }
          return 0;
      }
      

      fork() 之后的情况

      • fork() 返回 0:子进程
      • fork() 返回 >0:父进程,返回值是子进程的 PID
      • fork() 返回 -1:创建失败

      vfork()

      • vfork() 类似 fork(),但不会复制进程地址空间,适用于 exec 系列函数。

      exec()

      • exec() 用于执行新程序,会替换进程的代码段,如:
        execlp("ls", "ls", "-l", NULL);
        

         

      五) 进程状态

      Linux 进程的状态可以通过 ps auxtop 查看:

      状态 说明
      R (Running) 运行中或可运行
      S (Sleeping) 休眠(等待事件)
      D (Uninterruptible Sleep) 不可中断的睡眠(等待 I/O)
      T (Stopped) 进程已暂停
      Z (Zombie) 僵尸进程,已退出但未清理

      示例:

      ps -aux  # 查看所有进程
      top      # 实时监控进程
      

      六)进程调度

      Linux 采用 CFS(完全公平调度器) 来管理进程

      • 时间片调度(适用于普通进程
      • 优先级调度(实时进程
      • 调度策略
        • SCHED_OTHER:普通进程
        • SCHED_FIFO:先来先服务
        • SCHED_RR:轮转调度

      查看进程优先级(Nice 值):

      ps -eo pid,ni,comm
      

      调整进程优先级:

      nice -n 10 ./my_program  # 设置启动时的优先级
      renice -n 5 -p 1234       # 修改运行中进程的优先级
      

      七)进程通信

      7.1) 进程间通信(Inter-Process Communication, IPC)

      • Linux 支持经典的 Unix IPC 机制,包括 信号(Signals)、管道(Pipes)和信号量(Semaphores)
      • 还支持 System V IPC 机制,如 共享内存(Shared Memory)、信号量(Semaphores)和消息队列(Message Queues)
      • Linux 支持的 IPC 机制在 IPC 章节 中有详细描述。
      READ  Linux系统信息与管理命令清单

      7.2) 进程关联(Links)

      • Linux 系统中,没有任何进程是完全独立的,每个进程(除了第一个进程)都有 进程(Parent Process)
      • 进程并不是直接创建的,而是从现有进程 复制(Copy)克隆(Clone) 而来。
      • task_struct 结构体用于表示进程,它包含指向以下进程的指针:
      • 你可以使用 pstree 命令查看 Linux 系统中运行进程家族关系
        pstree
        
        pstree
        pstree

        此外,系统中的所有进程都存储在一个双向链表(doubly linked list)中,其根节点是 init 进程task_struct 结构体

        • 该链表使 Linux 内核 能够遍历系统中的所有进程
        • 这样设计的目的是为了支持诸如 ps(查看进程)和 kill(终止进程)等命令,确保内核可以高效地管理和操作所有进程

      7.3) 进程间通信(IPC)方式:

      • 管道(Pipe)pipe()
      • 消息队列(Message Queue)msgget()
      • 共享内存(Shared Memory)shmget()
      • 信号量(Semaphore)semget()
      • Socket 套接字socket()

      示例:管道通信

      #include <unistd.h>
      #include <stdio.h>
      
      int main() {
          int fd[2];  // 文件描述符
          pipe(fd);   // 创建管道
          if (fork() == 0) {
              close(fd[0]);  // 关闭读端
              write(fd[1], "Hello, parent!", 14);
              close(fd[1]);
          } else {
              char buf[20];
              close(fd[1]);  // 关闭写端
              read(fd[0], buf, sizeof(buf));
              printf("收到消息: %s\n", buf);
              close(fd[0]);
          }
          return 0;
      }
      

      八) 进程终止

      正常终止

      • exit()进程自行退出
      • returnmain() 返回

      异常终止

      • kill(pid, SIGKILL):强制终止
      • abort():产生 SIGABRT
      • segmentation fault (SIGSEGV):非法内存访问

      示例:终止进程

      kill -9 1234  # 杀死进程 1234
      pkill firefox # 杀死所有 firefox 进程
      

      九) 僵尸进程和孤儿进程

      在 Unix/Linux 系统中,正常情况下,子进程是通过父进程创建的,且两者的运行是相互独立的,父进程永远无法预测子进程到底什么时候结束。当一个进程调用 exit 命令结束自己的生命时,其实它并没有真正的被销毁,内核只是释放了该进程的所有资源,包括打开的文件、占用的内存等,但是留下一个称为僵尸进程的数据结构,这个结构保留了一定的信息(包括进程号 the process ID,退出状态,运行时间),这些信息直到父进程通过 wait()/waitpid() 来取时才释放。这样设计的目的主要是保证只要父进程想知道子进程结束时的状态信息,就可以得到

      9.1)僵尸进程(Zombie)

      一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程进程描述符仍然保存在系统中,这种进程称之为僵尸进程

      • 进程退出,但父进程未回收,仍占用 PID。
      • 解决方案
      wait(NULL);  // 父进程回收子进程
      

      9.2)孤儿进程(Orphan)

      一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为1)所收养,并由 init 进程对它们完成状态收集工作。

      9.3)僵尸进程与孤儿进程的问题危害

      僵尸进程虽然不占有任何内存空间,但如果父进程不调用 wait() / waitpid() 的话,那么保留的信息就不会释放,其进程号就会一直被占用,而系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害。

      READ  Linux系统信息与管理命令清单

      孤儿进程是没有父进程进程,孤儿进程这个重任就落到了 init 进程身上,init 进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

      9.4)如何解决僵尸进程造成的问题

      (1)方案一:父进程通过 waitwaitpid 等函数等待子进程结束,但这会导致父进程挂起,所以这并不是一个好办法,父进程如果不能和子进程并发执行的话,那我们创建子进程的意义就没有。同时一个 wait 只能解决一个子进程,如果有多个子进程就要用到多个 wait

      (2)方案二:通过信号机制:

      进程退出时,向父进程发送 SIGCHILD 信号,父进程处理 SIGCHILD 信号,在信号处理函数中调用 wait 进行处理僵尸进程

      (3)方案三:fork两次:

      原理是将进程成为孤儿进程,从而其的父进程变为 init 进程,通过 init 进程处理僵尸进程。具体操作为:父进程一次 fork() 后产生一个子进程随后立即执行 wait(NULL) 来等待子进程结束,然后子进程 fork() 后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为InitInit进程在其子进程结束时会自动收尸,这样也就不会产生僵死进程

      (4)方案四:kill进程

      严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过 kill 发送 SIGTERM 或者 SIGKILL 信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被 init 进程接管,init 进程会 wait() 这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能瞑目而去了。

      十) 进程管理命令

      Linux 系统中,进程管理是系统管理员和开发者的核心任务之一。以下是常用的 进程管理命令,帮助你监视、控制、终止和优化进程。执行中的程序称作进程。当程序可以执行文件存放在存储中,并且运行的时候,每个进程会被动态得分配系统资源、内存、安全属性和与之相关的状态。可以有多个进程关联到同一个程序,并同时执行不会互相干扰。操作系统会有效地管理和追踪所有运行着的进程
      为了管理这些进程,用户应该能够:

      • 查看所有运行中的进程
      • 查看进程消耗资源
      • 定位个别进程并且对其执行指定操作
      • 改变进程的优先级
      • 杀死指定进程
      • 限制进程可用的系统资源等

      Linux提供了许多命令来让用户来高效掌控上述的操作。

      命令 作用
      ps -aux 查看所有进程
      top 实时监控进程
      htop 交互式进程管理
      kill -9 <pid> 强制终止进程
      pkill <name> 根据进程名称杀死进程
      nice -n <priority> command 以指定优先级启动进程
      renice <priority> -p <pid> 调整进程优先级

      总结

      • Linux 进程是运行程序的实例,有 创建、运行、阻塞、终止 等状态。
      • fork() 复制进程exec() 运行新程序。
      • 进程通信方式包括 管道、共享内存、消息队列 等。
      • wait() 回收子进程,避免 僵尸进程
      • nicerenice 调整进程优先级。
      • killpkill 终止进程
        除教程外,本网站大部分文章来自互联网,如果有内容冒犯到你,请联系我们删除!

        发表回复

        您的邮箱地址不会被公开。 必填项已用 * 标注

        Leave the field below empty!

        Posted in 进程