1.冯诺依曼操作系统

结构常见实体(硬件)
1. 输入设备键盘、鼠标、麦克风、摄像头、扫描仪
2. 输出设备显示器、音箱、打印机、投影仪
3. 存储器内存条(RAM)、ROM、Cache(高速缓存)
4. 运算器CPU 中的 ALU(算术逻辑单元)
5. 控制器CPU 中的控制单元(CU)










2.操作系统

​




操作系统包括:

  • 内核(进程管理,内存管理,文件管理,驱动管理)
  • 其他程序(例如函数库,shell 程序等等)

作用

  • **先描述:**把要管理的东西弄成一个可以描述、量化 的 某种数据结构,可能是结构体
    • 例如:Linux 内核用 task_struct 描述进程,记录 PID、进程状态、调度信息等。
  • **再组织:**用链表、队列、哈希表等结构,把上面的结构体串起来,
  • **最后管理:**增删查改、调度。

描述是建立对象的信息模型,组织是建立对象之间的管理关系,管理则是对资源进行实际操作。











3.进程

进程 = 内核数据结构(task_struct) + 程序的代码和数据

描述

描述:把要管理的东西弄成一个可以描述、量化 的 某种数据结构,可能是结构体

  • PCB(process control block):所有操作系统都会有的,一种用来控制进程的 数据结构 ,可以理解为进程属性的集合。
  • task_ struct:Linux中的PCB。里面有各种各样的东西:pid:进程 ID;uid/gid:用户、组 ID等等



组织

多个task_struct连起来,形成一个双链表






获取PID

  • pid:进程 ID
  • ppid:父进程 ID
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    printf("pid: %d\n", getpid());
    printf("ppid: %d\n", getppid());
    return 0;
}






僵尸进程

  • 进程状态是Z状态
  • 原因
    • 子进程先退出,父进程还活着,但没调用 wait/waitpid 收它的退出信息。
    • 子进程的 task_struct(PCB)不会被内核删掉,就变成僵尸进程。
    • 它已经不跑代码、不占 CPU,只留一条进程记录放退出码,等父进程读取。
  • 危害:
    • 僵尸的 PCB 占内核进程表,大量僵尸会耗尽 pid 资源。
  • 解决:
    • 父进程 wait 回收;或者父进程先退出,子进程被 init 收养,init 会自动回收。



孤儿进程

  • 父进程先退出了,子进程还在运行,这个子进程就是孤儿进程
  • 孤儿进程会自动被 init 进程(pid=1) 收养,init 变成它的新父进程
    • 等孤儿进程结束的时候,init 会自动调用 wait 帮它回收,不会变成僵尸进程










4.进程调度




进程优先级

在linux或者unix系统中,用ps -l命令则会类似输出以下⼏个内容:

  • UID:代表执行者的身份
  • PID:代表这个进程的代号
  • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行
  • NI:代表这个进程的nice值,
    • PRI(new)=PRI(old)+nice
    • 当nice值为负值的时候,PRI将变小,即 优先级会变高,则其越快被执行
    • 调整进程优先级,在Linux下,就是调整进程nice值
    • nice其取值范围是-20⾄19,⼀共40个级别。
    • ⽤top命令可以更改已存在进程的nice





竞争、独立、并行、并发

  • 进程有优先级,因为CPU资源少,为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性:多进程运行, 运行期间互不干扰
  • 并行:多个进程在多个 CPU 下分别,同时进行运行,这称之为并行
  • 并发:多个进程在一个 CPU 下采用进程切换的方式,CPU来回切换进程,让多个进程都得以推进,称之为并发





进程切换

进程切换,就是CPU换一个进程来运行。

比如从A切到B:

  1. 保存A“执行到哪了、正在用哪些数据”。
  2. 恢复B上次保存的状态。
  3. B接着上次停下的地方继续运行。

这些需要保存的状态,就叫上下文,主要放在CPU寄存器里。

像做题做到一半,夹好书签,再换另一本书;回来时按书签继续。











5.环境变量

是什么

  • 一些参数,程序跑的时候能拿来参考

常见环境变量

  • PATH:指定命令的搜索路径
  • HOME:指定用户的主工作目录 (即用户登陆到 Linux 系统中时,默认的目录)
  • SHELL:当前 Shell, 它的值通常是 /bin/bash。

查看环境变量

echo $NAME //NAME:你的环境变量名称

getenv

  • 头文件:#include <stdlib.h>
  • 原型:char *getenv(const char *name);
  • 作用:C 程序里读取环境变量,相当于代码版 echo $xxx
  • 返回:找到 → 返回值字符串指针;找不到 → 返回NULL
#include <stdio.h>
#include <stdlib.h>
int main()
{
    char *path = getenv("PATH");
    if(path) printf("%s\n", path);
    return 0;
}











6.程序地址空间

之前学的

  • 之前学的:(如图,实际上并不是这样,这是)






为什么是虚拟地址空间

地址一样

  • 运行如下代码,输出出来的变量值和地址是⼀模⼀样的
  • 因为子进程按照父进程为模版,子并没有对变量进行进行任何修改。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return 0;
    }
    else if(id == 0){ //child
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }else{ //parent
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}




内容不一样

稍微改一下代码,变成下面这样,运行后就会发现我们发现:父子进程,输出地址是⼀致的,但是变量内容不⼀样!

说明,是虚拟地址!

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_val = 0;

int main()
{
    pid_t id = fork();
    if(id < 0){
        perror("fork");
        return 0;
    }
    else if(id == 0){ //child,子进程肯定先跑完,也就是子进程先修改,完成之后,父进程再读取
        g_val=100;
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }else{ //parent
        sleep(3);
        printf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    sleep(1);
    return 0;
}






进程地址空间

  • 实际上的地址空间是这样的:如图
  • 地址相同,其实是虚拟地址相同,内容不同其实是:被映射到了不同的物理地址!






虚拟内存管理

  • 进程的task_struct 结构中,有一个指针,指向一个特殊的结构体,结构体是mm_struct(内存描述符)。
  • mm_struct用来描述用户空间
  • 那么每⼀个进程都会有自己独立的 mm_struct(先描述),操作系统肯定是要将这么多进程的 mm_struct组织起来的!(再组织)
    • 具体怎么组织,可能用单链表,可能用红黑树










Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐