cen's blog cen's blog
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

cen

十年饮冰,难凉热血
首页
  • 编程文章

    • markdown使用
  • 学习笔记

    • 《JavaScript教程》
    • C++学习
    • C++数据结构
    • MySQL
    • Linux
  • 高中时代
  • 工作日常
  • CLion
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Linux环境搭建
  • 基本指令
  • 权限
  • Linux基础开发工具
  • 进程概念
    • 冯诺依曼体系
    • 操作系统
    • 进程概念
    • 描述进程-PCB
      • task_struct
      • task_struct内容分类
    • 组织进程
    • 查看进程
      • 系统目录查看
      • ps命令查看
    • 获取进程的PID和PPID
    • 创建进程
      • fork创建子进程
      • if分流
    • 进程状态
      • 运行状态-R
      • 浅度睡眠状态-S
      • 深度睡眠状态-D
    • 僵尸进程
    • 孤儿进程
    • 进程优先级
      • 引入
      • PRI和NI
      • 查看进程优先级
    • 环境变量
      • 常见环境变量
      • 环境变量相关命令
    • 进程地址空间
  • 进程控制
  • 基础IO流
  • 动态库和静态库
  • 进程通信
  • 进程信号
  • 多线程
  • 线程安全
  • Linux
cen
2025-02-11
目录

进程概念

# 冯诺依曼体系

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系:

冯诺依曼体系结构

# 操作系统

操作系统的定位:

简单来说,操作系统就是一款进行软硬件资源管理的软件。

设计操作系统的目的:

  1. 与硬件进行交互,管理所有的软硬件资源。
  2. 为用户程序(应用程序)提供一个良好的执行环境。

# 进程概念

  • 课本概念:程序的一个执行实例,正在执行的程序等
  • 内核观点:担当分配系统资源(CPU时间,内存)的实体

可执行程序本质上是一个文件,是放在磁盘上的。当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为只有加载到内存后,CPU才能对其进行逐行的语句执行,而一旦将这个程序加载到内存后,我们就不应该将这个程序再叫做程序了,严格意义上将应该将其称之为进程。如果右太多加载进来的程序,此时,操作系统就需要进行管理(先描述,后组织)。

先描述,后组织

# 描述进程-PCB

# task_struct

PCB实际上是对进程控制块的统称,在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含进程的信息。

进程 = 内核数据结构(task_struct) + 进程对应的磁盘代码

# task_struct内容分类

task_struct就是Linux当中的进程控制块,task_struct当中主要包含以下信息:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 任务状态,退出代码,退出信号等。
  • 优先级: 相对于其他进程的优先级。
  • 程序计数器(pc): 程序中即将被执行的下一条指令的地址。
  • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  • 上下文数据: 进程执行时处理器的寄存器中的数据。
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟总和,时间限制,记账号等。
  • 其他信息。

# 组织进程

可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

# 查看进程

# 系统目录查看

进程的信息可以通过bin/proc系统文件夹查看

比如:要获取PID为2565167的进程信息,你需要查看 /proc/2565167 这个文件夹

# ps命令查看

使用ps aux可以打印出当前所有进程的信息。

比如:要获取PID为2565167的进程信息,你需要查看进行以下命令:

[cen@VM-8-2-opencloudos proc]$ ps aux | head -1 && ps aux | grep 2565167
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cen      2565167  0.0  0.1  13856  1808 pts/2    S+   15:54   0:00 ./test
cen      2567165  0.0  0.0 222012  1096 pts/3    S+   15:59   0:00 grep --color=auto 2565167
1
2
3
4

# 获取进程的PID和PPID

进程id(PID) 父进程id(PPID)

#include<iostream>

#include <unistd.h>
using namespace std;

#include<sys/types.h>

int main() {
  while(1) {
    sleep(20);
    cout << "我是一个进程:" << "PID:" << getpid() << " PPID:" << getppid() << endl;
  }
  return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

我是一个进程:PID:2578545 PPID:2577206

[cen@VM-8-2-opencloudos ~]$ ps aux | head -1 && ps aux | grep test
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cen      2578545  0.0  0.1  13856  1892 pts/4    S+   16:30   0:00 ./test
cen      2579086  0.0  0.0 222012  1180 pts/5    S+   16:31   0:00 grep --color=auto test
[cen@VM-8-2-opencloudos ~]$ ps aux | head -1 && ps aux | grep 2577206
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cen      2577206  0.0  0.2 226016  5144 pts/4    Ss   16:27   0:00 -bash
cen      2579349  0.0  0.0 222012  1120 pts/5    S+   16:32   0:00 grep --color=auto 2577206
1
2
3
4
5
6
7
8

由此,命令行上启动的命令,它的父进程一般都是bash

# 创建进程

# fork创建子进程

**fork函数创建子进程:**fork是一个系统调用级别的函数,其功能就是创建一个子进程。(函数执行前,只有父进程;函数执行后,父进程 + 子进程) 返回值:

  • 如果子进程创建成功,在父进程中返回子进程的PID,而在子进程中返回0。
  • 如果子进程创建失败,则在父进程中返回 -1。

实际上,使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行。需要注意的是,父子进程虽然代码共享,但是父子进程的数据各自开辟空间(采用写时拷贝)并且通过返回值的不同,来让父子进程执行不同的代码。

#include<iostream>
#include <unistd.h>
using namespace std;
#include<sys/types.h>
int main() {
  fork();
  cout << "我是一个进程:" << "PID:" << getpid() << " PPID:" << getppid() << endl;
  return 0;
}
1
2
3
4
5
6
7
8
9
[cen@VM-8-2-opencloudos ~]$ ./test
我是一个进程:PID:2607219 PPID:2577206
我是一个进程:PID:2607220 PPID:2607219
1
2
3

分析:运行cpp程序,父进程调用fork(),产生一个子进程,fork()调用后,父子两个进程分别执行cout语句,输出各自的PID和PPID。 例如:父进程开始,PID:2607219 PPID:2577206(bash的PID),子进程开始,PID:2607220 PPID:2607219(父进程的PID)。

# if分流

据返回值的不同来让父子进程执行不同的代码,从而做不同的事。

#include<iostream>

#include <unistd.h>
using namespace std;

#include<sys/types.h>


int main() {
    pid_t id = fork();
    if(id == 0) {
        while(1) {
          cout << "子进程..." << endl;
          sleep(2);
      }
    }
    else if(id > 0) {
        while(1) {
          cout << "父进程" << endl;
          sleep(2);
        }
    }
    else
        // fork error
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

输出结果:父进程、子进程循环打印。

# 进程状态

Linux操作系统的源代码当中对于进程状态的定义:

/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 运行状态-R

一个进程处于运行状态(running),并不意味着进程一定处于运行当中,运行状态表明一个进程要么在运行中,要么在运行队列里。也就是说,可以同时存在多个R状态的进程。

# 浅度睡眠状态-S

一个进程处于浅度睡眠状态(sleeping),意味着该进程正在等待某件事情的完成,处于浅度睡眠状态的进程随时可以被唤醒,也可以被杀掉(这里的睡眠有时候也可叫做可中断睡眠(interruptible sleep))。

# 深度睡眠状态-D

一个进程处于深度睡眠状态(disk sleep),表示该进程不会被杀掉,即便是操作系统也不行,只有该进程自动唤醒才可以恢复。该状态有时候也叫不可中断睡眠状态(uninterruptible sleep),处于这个状态的进程通常会等待IO的结束。

例如,某一进程要求对磁盘进行写入操作,那么在磁盘进行写入期间,该进程就处于深度睡眠状态,是不会被杀掉的,因为该进程需要等待磁盘的回复(是否写入成功)以做出相应的应答。(磁盘休眠状态)

# 僵尸进程

进程在退出的时候,不能立即释放对用的资源,保存一段时间后,让父进程或OS来进行读取,如果没有回收,此时的状态就是僵尸状态。

僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。

# 孤儿进程

如果父进程先退出,子进程就称之为“孤儿进程”。孤儿进程被1号init进程领养,退出的时候被init进程回收。

如果一个进程变成孤儿,会自动变成后台进程。

# 进程优先级

# 引入

cpu资源分配的先后顺序,就是指进程的优先权(priority)。优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能,还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大改善系统整体性能。

# PRI和NI

执行ps -l来查看系统进程:

F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S  1001  365554  365551  0  80   0 -  2063 do_wai pts/0    00:00:00 bash
0 R  1001  365795  365554  0  80   0 -  2736 -      pts/0    00:00:00 ps
1
2
3

其中:

  • UID : 代表执行者的身份
  • PID : 代表这个进程的代号
  • PPID :代表这个进程是由哪个进程发展衍生而来的,即父进程的代号
  • PRI :代表这个进程可被执行的优先级,值越小越早被执行
  • NI :代表这个进程的nice值

理解: NI就是我们所要说的nice值,其表示进程可被执行的优先级的修正数值,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new) = PRI(old)(80) + nice,所以,调整进程优先级,在Linux下,就是调整进程nice值(PRI(old)默认等于80,nice其取值范围是-20至19,一共40个级别)。

# 查看进程优先级

  • ps -al

使用ps -al命令查看该进程优先级的信息

  • top 通过top命令更改进程的nice值,top命令就相当于Windows操作系统中的任务管理器,它能够动态实时的显示系统当中进程的资源占用情况。

具体:按R,输入更改进程的UID,进行更改nice值,最后按Q退出

# 环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

# 常见环境变量

  • PATH : 指定命令的搜索路径
  • HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)
  • SHELL : 当前Shell,它的值通常是/bin/bash
  • ...
  1. 查看环境变量方法: echo $NAME NAME:你的环境变量名称
  2. 代码获取环境变量的方法: cout << getenv("PATH") << endl;或者通过第三方变量environ获取
	extern char **environ;  // 一个环境变量指针数组,指向HOME、SHELL等等
	int i = 0;
	for(; environ[i]; i++){
	    cout << environ[i] << endl;
 	}
1
2
3
4
5

# 环境变量相关命令

  1. echo: 显示某个环境变量值
  2. export: 设置一个新的环境变量
  3. env: 显示所有环境变量
  4. unset: 清除环境变量
  5. set: 显示本地定义的shell变量和环境变量

# 进程地址空间

进程地址空间就类似于一把尺子,尺子按照刻度被划分为各个区域(内核空间约1GB,其余部分占3GB)。 例如代码区、堆区、栈区等。而在结构体mm_struct当中,便记录了各个边界刻度,例如代码区的开始刻度与结束刻度,如下图所示:

在结构体mm_struct当中,各个边界刻度之间的每一个刻度都代表一个虚拟地址, 这些虚拟地址通过页表映射与物理内存建立联系。由于虚拟地址是线性增长的,所以虚拟地址又叫做线性地址。

进程地址空间里面不是物理地址,而是虚拟地址(线性地址)。物理地址用户一概是看不到的,是由操作系统统一进行管理的(通过页表映射:进程 -- 映射 --> 物理地址)

上次更新: 2025/03/14, 11:20:46
Linux基础开发工具
进程控制

← Linux基础开发工具 进程控制→

最近更新
01
线程安全
05-21
02
cmake教程
05-08
03
项目
05-07
更多文章>
Theme by Vdoing | Copyright © 2024-2025 京ICP备2020044002号-3 京公网安备11010502056119号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式