本帖最后由 qnpragyl 于 2024-5-9 17:01 编辑
一、 进程概念进程是计算机中运行的程序的实例。每个进程都有自己的内存空间、代码和数据,它们相互隔离,互不干扰。进程可以通过操作系统的调度机制来执行,并且可以同时运行多个进程。每个进程都有一个唯一的进程标识符(PID),用于操作系统识别和管理进程。进程可以与其他进程进行通信和协作,例如通过共享内存、管道或套接字等机制。进程的创建、调度、销毁等操作由操作系统负责管理。 (Linux内核中把进程叫做任务(task),进程的虚拟地址空间可分为用户虚拟地址空间和内核虚拟地址空间,所有进程共享内核虚拟地址空间,每个进程有独立的用户虚拟地址空间。) 1、 进程有俩种特殊的形式:没有用户虚拟地址空间的进程叫做内核线程,共享用户虚拟地址空间的进程叫用户线程。共享同一个用户虚拟地址空间的所有用户线程叫线程组。 俩种进程的对应名称 C语言标准库进程 | Linux内核进程 | 包括多个线程的进程 | 线程组 | 只有一个线程的进程 | 任务或者进程 | 线程 | 共享用户虚拟地址空间的进程 |
Linux通过:ps命令用于输出当前系统的进程状态(查看进程运行状态),显示瞬间进程的状态,并不是动态连续;
各列的详细信息
- USER:启动该进程的用户名。
- PID:进程的唯一标识符(PID)。
- %CPU:该进程占用 CPU 的使用百分比。
- %MEM:该进程占用系统内存的百分比。
- VSZ:该进程使用的虚拟内存大小(以 KB 为单位)。
- RSS:该进程使用的实际物理内存大小(以 KB 为单位)。
- TTY:启动该进程的终端设备(如果有)。
- STAT:进程的状态。常见的状态包括 S(休眠)、R(运行)、D(不可中断的休眠)、Z(僵尸)等。
- START:进程启动的时间。
- TIME:该进程消耗的 CPU 时间。
- COMMAND:启动该进程的命令及参数。
top指令能够对进程进行实时的监控
命令输出的各部分解释 · top - 20:00:09 up 2:25, 1 user, load average: 0.20, 0.05, 0.02:这行显示了当前系统时间、系统已运行时间、当前登录用户数量以及系统的平均负载(过去1分钟、5分钟和15分钟的平均负载)。 · Tasks: 320 total, 1 running, 240 sleeping, 0 stopped, 0 zombie:这行显示了系统中的任务数量,包括运行中的任务、休眠中的任务、已停止的任务和僵尸任务的数量。 · %Cpu(s): 0.2 us, 0.1 sy, 0.0 ni, 99.7 id, 0.0 wa, 0.0 hi, 0.0 si,0.0 st:这行显示了 CPU 使用情况的统计信息,包括用户空间时间、系统内核时间、用户空间低优先级时间、空闲时间、等待I/O 时间、硬中断处理时间、软中断处理时间和虚拟化环境中的虚拟CPU 闲置时间。 · KiB Mem 和 KiB Swap:这两行显示了系统内存和交换空间的使用情况,包括总量、空闲量、已使用量和可用量。
在top基本视图中,按键盘数字“1”,可以监控每个逻辑CPU的状况:
显示指定的进程信息 top -p 1809
二、 进程生命周期
Linux操作系统属于多任务操作系统,系统中的每个进程能够分时复用CPU时间片,通过有效的进程调度策略实现多任务并行执行。而进程在被CPU调度运行,等待CPU资源分配以及等待外部事件时会属于不同的状态。进程状态如下:创建状态:创建新进程;就绪状态:进程获取可以运作所有资源及准备相关条件;执行状态:进程正在CPU中执行操作;阻塞状态:进程因等待某些资源而被跳出CPU;终止状态:进程消亡。
Linux内核进程状态间关系如图:
1、 Linux内核提供API函数来设置进程的状态:l TASK_RUNNING (可运行态或者可就绪状态)l TASK_INTERRUPTIBLE (可中断睡眠状态,又叫浅睡眠状态)l TASK_UNINTERRUPTIBLE (不可中断状态,深度睡眠状态)可以通过ps命令查看被标记为D状态的进程l __TASK_STOPPEF(终止状态)l EXIT_ZOMBIE (僵尸状态) :当一个进程终止时,其父进程通常需要调用 wait() 系统调用来获取子进程的终止状态。如果父进程没有调用 wait(),而子进程已经终止,那么子进程的进程描述符(ProcessDescriptor)仍然会被保留在系统中,但进程已经不再存在
三、 task_struct数据结构分析
在Linux内核中采用task_struct结构体来描述专程控制块。Linux内核涉及进程和程序的所有算法都围绕名为task_struct的数据结构而建立操作的。
核心成员组结构体注释:
- /* -1 unrunnable, 0 runnable, >0 stopped: */
- volatile long state; //进程的状态标志
- /*
- * This begins the randomizable portion of task_struct. Only
- * scheduling-critical items should be added above here.
- */
- randomized_struct_fields_start
- void *stack; // 指向内核栈
- refcount_t usage;
- /* Per task flags (PF_*), defined further below: */
- unsigned int flags;
- unsigned int ptrace;
- pid_t pid; //全局的进程号
- pid_t tgid; // 全局的线程组标识符
1. struct hlist_node pid_links[PIDTYPE_MAX];//进程号,进程组表示符和会话标识符
- struct task_struct __rcu *real_parent; //指向真实的父进程
- /* Recipient of SIGCHLD, wait4() reports: */
- struct task_struct __rcu *parent; // 指向父进程
- /*
- * Children/sibling form the list of natural children:
- */
- struct list_head children;
- struct list_head sibling;
- struct task_struct *group_leader; //指向线程组的组长
- /* Objective and real subjective task credentials (COW): */
- const struct cred __rcu *real_cred; //此成员指向主体和真实客体的证书
- /* Effective (overridable) subjective task credentials (COW): */
- const struct cred __rcu *cred; //指向有效客体证书 (可以被临时改变)
- /*
- * executable name, excluding path.
- *
- * - normally initialized setup_new_exec()
- * - access it with [gs]et_task_comm()
- * - lock it with task_lock()
- */
- char comm[TASK_COMM_LEN]; //进程名称
- struct nameidata *nameidata;
- /*下面四个是进程调度策略和优先级*/
- int prio;
- int static_prio;
- int normal_prio;
- unsigned int rt_priority;
- //这个两指针指向内存描述符。进程:mm和active_mm指向同一个内存描述符。内核线程:mm是空指针
- //当内核线程运行时,active_mm指向从进程借用内存描述符
- struct mm_struct *mm;
- struct mm_struct *active_mm;
- 、/* Filesystem information: */
- struct fs_struct *fs; //此成员文件系统信息,主要是进程的根目录和当前工作目录
- /* Open file information: */
- struct files_struct *files; // 打开文件表
- /* Namespaces: */
- struct nsproxy *nsproxy; // 命名空间
- //下面这一块的成员主要是用于 信号处理
- /* Signal handlers: */
- struct signal_struct *signal;
- struct sighand_struct __rcu *sighand;
- sigset_t blocked;
- sigset_t real_blocked;
- /* Restored if set_restore_sigmask() was used: */
- sigset_t saved_sigmask;
- struct sigpending pending;
四、 进程优先级/系统调用1、 进程优先级l 限期进程的优先级比实时进程要高,实时进程的优先级比普通进程要高。l 限期进程的优先级是-1;l 实时进程的优先级1-99,优先级数值越大,表示优先级越高;l 普通进程的静态优先级为:100-139,优先级数值越小,表示优先级越高,可通过修改nice值改变普通进程的优先级,优先级等于120加上nice值。
如下表: 优先级 | 限期进程 | 实时进程 | 普通进程 | prio调度优先级 (数值越小,优先级越高) | 大多数情况下prio等于normal_prio。特殊情况,如果进程x占有实时互斥锁,进程y正在等待锁,进程y的优先级比进程进程x优先级高,那么把进程x的优先级临时提高到进程y的优先级,即进程x的prio的值等于进程y的prio值。 | static_prio静态优先级 | 没有意义,总是0 | 没有意义,总是0 | 120+nice值,数值越小,表示优先级越高 | normal_prio正常优先级 (数值越小优先级越高) | -1 | 99- rt_priority | static_prio | rt_priority 实时优先级 | 没有意义,总是0 | 实时进程的优先级,范围1-99,数值越大优先级越高 | 没有意义,总是0 |
2、 系统调用当运行应用程序的时候,调用fork()/vfork()/clone()/open()/write()函数就是系统调用。系统调用就是应用程序如何进入内核空间执行任务,程序使用系统调用执行一系列操作:比如创建进程、文件IO等等。具体如下图:
内核源码:
在 Linux 内核中定义了 fork() 系统调用的实现。在启用 MMU 支持的情况下,它会调用 _do_fork() 函数来创建一个新的子进程。
- SYSCALL_DEFINE0(fork)
- {
- #ifdef CONFIG_MMU
- struct kernel_clone_args args = {
- .exit_signal = SIGCHLD,
- };
- return _do_fork(&args);// 调用 _do_fork() 函数
- #else
- /* can not support in nommu mode */
- return -EINVAL;
- #endif
- }
代码定义了 vfork() 系统调用的实现,会在子进程运行期间暂停父进程的执行。
- #ifdef __ARCH_WANT_SYS_VFORK
- SYSCALL_DEFINE0(vfork)
- {
- struct kernel_clone_args args = {
- .flags = CLONE_VFORK | CLONE_VM,
- .exit_signal = SIGCHLD,
- };
- return _do_fork(&args);// 调用 _do_fork() 函数
- }
- #endif
实现了 clone() 系统调用的逻辑,允许创建新的进程或线程,并传递各种参数以定制进程的行为。
- #else
- SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
- int __user *, parent_tidptr,
- int __user *, child_tidptr,
- unsigned long, tls)
- #endif
- {
- struct kernel_clone_args args = {
- .flags = (lower_32_bits(clone_flags) & ~CSIGNAL),
- .pidfd = parent_tidptr,
- .child_tid = child_tidptr,
- .parent_tid = parent_tidptr,
- .exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),
- .stack = newsp,
- .tls = tls,
- };
- if (!legacy_clone_args_valid(&args))
- return -EINVAL;
- return _do_fork(&args);// 调用 _do_fork() 函数
- }
- #endif
这段代码是 _do_fork() 函数的实现,用于执行实际的进程复制(fork)操作。
- long _do_fork(struct kernel_clone_args *args)
- {
- u64 clone_flags = args->flags;
- struct completion vfork;
- struct pid *pid;
- struct task_struct *p;
- int trace = 0;
- long nr;
- /*
- * Determine whether and which event to report to ptracer. When
- * called from kernel_thread or CLONE_UNTRACED is explicitly
- * requested, no event is reported; otherwise, report if the event
- * for the type of forking is enabled.
- */
- if (!(clone_flags & CLONE_UNTRACED)) {
- if (clone_flags & CLONE_VFORK)
- trace = PTRACE_EVENT_VFORK;
- else if (args->exit_signal != SIGCHLD)
- trace = PTRACE_EVENT_CLONE;
- else
- trace = PTRACE_EVENT_FORK;
- if (likely(!ptrace_event_enabled(current, trace)))
- trace = 0;
- }
- p = copy_process(NULL, trace, NUMA_NO_NODE, args);// 调用 copy_process() 函数创建新的进程。传递给 copy_process() 函数的参数包括父进程的地址空间、需要报告给 ptracer 的事件类型、NUMA 节点编号和进程复制的参数。
- add_latent_entropy();
代码定义了一个名为 do_fork() 的函数,用于在不支持复制线程 TLS(Thread Local Storage)的架构上创建新的进程。
- #ifndef CONFIG_HAVE_COPY_THREAD_TLS
- /* For compatibility with architectures that call do_fork directly rather than
- * using the syscall entry points below. */
- long do_fork(unsigned long clone_flags,//创建进程的标志位集合
- unsigned long stack_start,//用户态栈的起始地址
- unsigned long stack_size,//用户态栈的大小,一般情况下设置为0
- int __user *parent_tidptr,//指向用户空间中地址的指针,分配指向父子进程的PID
- int __user *child_tidptr)
- {
- struct kernel_clone_args args = {
- .flags = (lower_32_bits(clone_flags) & ~CSIGNAL),
- .pidfd = parent_tidptr,
- .child_tid = child_tidptr,
- .parent_tid = parent_tidptr,
- .exit_signal = (lower_32_bits(clone_flags) & CSIGNAL),
- .stack = stack_start,
- .stack_size = stack_size,
- };
- if (!legacy_clone_args_valid(&args))
- return -EINVAL;
- return _do_fork(&args); // 调用 _do_fork() 函数来执行实际的进程复制操作,并返回其返回值。
- }
- #endif
3、 内核线程 内核线程是直接由内核本身启动的进程。内核线程实际上是将内核函数委托给独立的进程,与系统中其他进程“并行”执行(实际上,也并行于内核自身的执行)。内核线程经常称之为(内核)守护进程。它们用于执行下列任务。(task_struct数据结构里面有一个成员指针mm设置为NULL,它只能运行在内核空间。) l 周期性地将修改的内存页与页来源块设备同步(如mmap的文件映射); l 如果内存页很少使用,则写入交换区; l 管理延时动作;
l 实现文件系统的事务日志。 创建一个内核线程
- /*
- * Create a kernel thread.
- */
- pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
- {
- struct kernel_clone_args args = {
- .flags = ((lower_32_bits(flags) | CLONE_VM |
- CLONE_UNTRACED) & ~CSIGNAL),
- .exit_signal = (lower_32_bits(flags) & CSIGNAL),
- .stack = (unsigned long)fn,
- .stack_size = (unsigned long)arg,
- };
- return _do_fork(&args);// 调用 _do_fork(&args) 函数来执行实际的线程创建操作,并返回其返回值,即新线程的进程 ID。
- }
4、 退出进程 进程主动终止:从main()函数返回,链接程序会自动添加到exit()系统函数;
进程被动终止:进程收到一个自己不能处理的信号;进程收到SIGKILL等终止信息。 这段代码定义了一个名为 exit 的系统调用,接受一个整数参数作为错误码,然后使用 do_exit() 函数终止当前进程并返回指定的错误码。
- SYSCALL_DEFINE1(exit, int, error_code)// 将系统调用映射到内核中的实际函数
- {
- do_exit((error_code&0xff)<<8);// do_exit() 函数用于终止当前进程,其中 error_code 参数被左移 8 位,并通过按位与操作确保只取低 8 位的值。
- }
|