Linux进程管理

Posted by Run-dream Blog on June 8, 2021

进程管理

什么是进程

进程就是运行中的程序。

通过ps可以看到在linux中有几类进程

  • 不带括号的进程 表示这是用户态进程 如init进程
  • 带括号[] 的进程 表示内核态的进程 如 kethradd 内核现场
  • 带?的进程 说明不是前台启动的,一般都是后台的服务 如 tty

多进程缺点: 创建进程占用资源多; 进程间通信需拷贝内存, 不能共享

线程

一条线程指的是进程中一个单一顺序的控制流。

线程的数据细分成三类:

  1. 线程栈上的本地数据,每个线程都有自己的栈空间,默认大小为8M (针对于单个函数)
  2. 整个进程里共享的全局数据,可能冲突
  3. 线程私有数据

共享数据的保护机制

mutex 在共享数据访问的时候,去申请加把锁

  1. lock(没抢到则阻塞)
  2. trylock(没抢到则返回错误码);
  3. 条件变量(通知), 收到通知, 还是要抢锁(由 wait 函数执行); 因此条件变量与互斥锁配合使用

对应数据结构

内核中进程, 线程统一为任务, 由 task_struct 表示 也就是操作系统课程中的PCB的概念 通过链表串起 task_struct

对于一个正在运行的进程,可以通过以下命令行查看进程详细信息

ps 
cat /proc/pid

进程数据结构

  • 任务ID

    通过对比 pid 和 tgid 可判断是进程还是线程

  • 任务状态

    任务状态

    对应操作系统进程的状态:

    • 就绪 TASK_RUNNING
    • 运行
    • 阻塞 TASK_INTERRUPTIBLE TASK_UNITERRUPTIBLE TASK_KILLABLE
  • 权限相关

    • real_cred 谁能操作此进程
    • cred 此进程能操作谁 chmod
      • uid gid 启动
      • euid egid 生效
      • fsuid fsgid 文件

    capabilities 机制 将root权限拆分成不同的单元,用 getcapsetcap 命令 来允许普通用户来执行

  • 内核栈

    负责将用户态的执行和内核态的执行串起来。

    将用户态运行过程中的 CPU 上下文保存起来,这样当从内核系统调用返回的时候,才能让进程在刚才的地方接着运行下去。

    内核栈

    在内核态,32 位和 64 位的内核栈和 task_struct 的关联关系不同。32 位主要靠 thread_info,64 位主要靠 Per-CPU 变量。

调度

进程的分类:

  • 实时进程 优先级的范围是 0~99

    调度策略

    • SCHED_FIFO 调度类 rt_sched_class 根据policy区分
    • SCHED_RR 调度类 rt_sched_class 根据policy区分
    • SCHED_DEADLINE dl_sched_class
  • 普通进程 优先级的范围是 100~139

    调度策略

    • SCHED_NORMAL fair_sched_class
    • SCHED_BATCH
    • SCHED_IDLE idle_sched_class

对应的数据结构

一个 CPU 上有一个队列来表示所有的进程,完全公平调度算法(CFS) 的队列是一棵红黑树,树的每一个节点都是一个 sched_entity,每个 sched_entity 都属于一个 task_struct,task_struct 里面有指针指向这个进程属于哪个调度类。

任务调度

设置进程和线程的调度策略

 # 查看当前进程的调度策略
 chrt -p <pid>
 # 修改当前进度的调度优先级和策略
 chrt -f -p | -i | -o <priority> <pid>
  1. 主动调度
  2. 抢占调度

主动调度

如何查看进程的运行时间和上下文切换次数

cat /proc/<pid>/sched

进程创建

fork

  1. 将 task_struct 结构复制一份并且初始化
  2. 试图唤醒新创建的子进程。

fork

线程创建

线程不是一个完全由内核实现的机制,它是由内核态和用户态合作完成的。

pthread_create 不是一个系统调用,是 Glibc 库的一个函数。

  • 内核态创建任务
  • 用户态执行线程

创建进程的话,调用的系统调用是 fork,在 copy_process 函数里面,会将五大结构 files_struct、fs_struct、sighand_struct、signal_struct、mm_struct 都复制一遍,从此父进程和子进程各用各的数据结构。

而创建线程的话,调用的是系统调用 clone,在 copy_process 函数里面, 五大结构仅仅是引用计数加一,也即线程共享进程的数据结构。

进程创建和线程创建的区别