【Linux】进程概念

1、进程的概念

(1)结构体+代码数据构成进程,而不是说把可执行程序从磁盘拿到内存就是进程了,还包括描述进程的部分。

(2)在操作系统里,这个结构体叫做PCB,Linux里叫做task struct(进程控制块),是一个双链表。

(3)我的第一个进程

2、查看进程PID

(1)两种方式 ; &&查看正在运行的进程

(2)ls /proc目录下,是各个进程的PID创建的目录

(3)exe:进程对应的可执行程序,要是删掉后,正在跑的进程还能继续,充分说明已经把exe文件从磁盘考到内存了,删除的只是磁盘上的exe文件

(4)cwd 记录当前进程的所在目录,如果在.c文件中用fopen创建一个.txt文件,这个.txt文件会自动创建在cwd指定的当前目录下。但是也可以更改进程的目录,在myprocess.c中,用chdir(新的目录),然后再用fopen打开新的文件,运行进程后查看文件,这个文件一定是在新的目录底下的。

3、杀掉进程

ctrl+c

kill -9 进程PID号

4、父进程

进程是由父进程创建的;父进程就是bash,os会给每一个登录用户分配一个bash

下面这个就是一个bash打出来的一个字符串,为啥能卡在这里等待用户输入,scanf一样,用户输入后交给bash这个进程去解析用户的命令,因此更能理解:bash也是一个进程。

5、代码创建子进程 fork()

像ls touch...这些都是子进程,都有一个父进程bash创建的,

执行结果如下,为啥会输出两次"进程开始运行"?

因为子进程的PCB是拷贝父进程的PCB,因此指向同一段数据和代码,一个是父进程本身,一个是子进程,都会打印自己的进程id。fork()之后的代码,父子进程都会执行。

6、fork()会有两个返回值

创建成功:①子进程PID给父进程;②0给子进程;

创建失败:返回-1

猜想下面代码运行:

实际运行结果:

解析:19811是bash进程,当前进程(父进程):1620,创建的子进程是1621

Q:

A1:因为系统中通常是1个父进程,n个子进程;父进程将来要管理多个子进程,所以拿到子进程的pid,才能区分每个子进程

A2:fork()创建一个子进程后,fork内部的最后的return语句也会被子进程运行,因此有两个返回值

A3:先理解下面的第7个小点

return 返回的也是一个值,每次也是把变量往id里写,因此会发生写时拷贝,不管谁先赋值,都可以拿到自己对应的那个id号,也就是会有两个id,因此一个id==0,另外一个id>0。更深刻的理解:父进程和子进程拿着同一个虚拟地址,但是最终找到了不同的物理地址,所以一个等于0一个大于0.

7、进程是独立的

(1)数据结构独立,因为进程等于内核数据结构

(2)代码共享,代码是只读的不影响独立性

(3)数据以写实拷贝的方式,各自私有一份,以此保证独立性

eg.有一个全局变量 gval,初始值=100;子进程每次修改gval+=10,子进程看到的结果改变;但是父进程看到的值依旧是100,这是因为写实拷贝,把gval拷贝了一份给子进程,子进程每次修改拷贝的这个gval,而不是原始的gval.

8、进程状态(粗)

(1)运行:进程在调度队列里面

(2)阻塞:把进程的PCB挂到相应的硬件设备的wait队列中,也就是该进程需要的硬件资源不满足

(3)阻塞挂起:把等待硬件资源的进程挂起

进程在内存中等待键盘,内存中存入多个进程(PCB+代码数据),导致内存不够,把进程的代码和数据拿到磁盘的swap交换区里,内存只保留这些进程的PCB,叫做阻塞挂起。①唤出:把代码和数据拿出去②唤入:把代码和数据拿进来(到内存)

"挂起":就是把代码和数据"挂"到磁盘上

(4)就绪挂起,把调度队列里面的进程挂起

有一个PCB就够了,一个PCB可以在不同的数据结构里都有,其实是同一个

9、Linux进程状态(细)

(1)R:运行

(2)S:阻塞/休眠,比如scanf等待键盘输入,这个进程不被调度了。可中断休眠,浅睡眠,scanf正在等待,我们可以ctrl+C中断休眠

(3)T:暂停,①追踪状态,给程序打断点,因为进程被暂停了②ctrl+Z暂停这个进程

(4)D:阻塞,不可中断休眠,深度睡眠

(5)X:死亡状态

(6)Z:僵尸状态,为了获取退出信息(PCB)!子进程退出后,父进程获取子进程信息之前,该进程处于僵尸状态。

(7)内存泄漏问题:进程一旦退出,哪怕程序中的代码本身会有内存泄漏。比如new 后没有delete,但是进程退出后,就不会有内存泄漏问题。有问题的是常驻内存的进程,比如操作系统本身,这个就需要用户手动去解决。

(8)slab技术:方便创建进程的task_struct,这些task_struct可以被复用

10、孤儿进程

父进程退出后,剩子进程了?下面是一个父进程先于子进程退出的代码,读者可以思考会发生什么

结果如下,父进程是02,子进程是03,父进程退出后,子进程的PPID变成了1 ,此时就是孤儿进程了。父母不在了,被1(孤儿院)领养。要是不被领养,等到子进程进入僵尸态的时候,没人管他,子进程信息没人回收,就会造成内存泄漏,所以必须有1系统去管理。孤儿进程就变成后台进程了,不能在终端用ctrl+c中断,只能kill

11、进程优先级

(1)PRI(默认80不变)+NI(偏置值,用户可以修改)

(2)改进程的优先级:

注意nice值,也就是在80的基础上能偏移多少,往左80-20=60,往右80+19=99

并发:只有一个CPU,只是进程切换的非常快,导致用户感觉像是多个CPU,但其实是顺序执行

12、进程切换

(1)寄存器就是CPU内部的临时空间

(2)具体

进程A切换走的时候,会把CPU内所有寄存器的内容拷贝一份(硬件上下文数据)带走,进程A回来的时候用这个数据再恢复一次寄存器中的值。

把硬件上下文数据保存到task_struct

(3)调度要做的:①切换②选择进程

(4)进程优先级其实有140个,前100是实时优先级,后40是分时优先级。前100个我们用不到,比如实时操作系统,在汽车刹车,工业生产流水线上必须用实时优先级,不能等时间片分配到才去响应。

(5)问题:既然进程的默认优先级是80,想要修改进程的优先级,为啥不直接修改80为60,而是需要Nice=-20来设置优先级为60?

  • 明确 O (1) 调度器的核心机制:active/expired 队列

进程先都在active指向的strcut rqueue_elem中所维护的队列,当这个队列里的进程都运行完之后,会交换active和expired的指针,再去运行expired指向的strcut rqueue_elem中的进程,这样保证了局部按照FIFO的规则调度进程上CPU,因为要是有一个进程在60,它运行完后还要继续运行,把它再次挂到60上,那后续的90的进程一直得不到CPU,就会处于饥饿状态,因此需要expired。这就是O(1)调度器的核心机制。

  • "默认优先级 80(实际 120)" 是队列分组的基准,不能直接改

比如当前这个进程优先级是80,在cpu上运行到时间片截止,但进程还没运行完,此时若想提高该进程的优先级,一定是Nice=-20,使得这个进程从active进程expired后,在expired队列里,可以利用Nice值将进程挂在60的队列里;但要是直接修改基准值为60,那么expired里的所有进程默认都是60了。

13、环境变量

(1)存储:环境变量在bash这个进程里面存着,被bash去维护的;即环境变量在bash进程的上下文里。bash进程启动的时候会加载.bash_profile(配置文件),要是把自己的文件路径添加进去之后,下一次打开bash直接就能运行自己的命令了。

(2)两张表,bash中会有两张表,一张环境变量表 ,一张命令行参数表(存储用户的命令行输入的参数),但这两张表存储的都是字符串。

(3)用户登录时,就有bash进程启动,bash进程会维护这两张表。首先拿着用户命令行输入的命令去找命令行参数表去解析命令,二一个就是根据用户当前的地址去查看环境变量表中的PATH有没有用户的地址,有的话用户就可以直接执行当前的命令了。因此,指令的查找工作,是由bash自己完成的。

(4)更多环境变量

HISTSIZE=1000用户用上下键,找到历史的1000条命令

bash用环境变量了,其实就是用户用了。

(5)和环境变量相关的命令

(6)通过代码获取环境变量

cpp 复制代码
#include<stdio.h>
int main(int argc, char *argv[], char *env[])
 {
    int i = 0;
    for(; env[i]; i++){
        printf("%s\n", env[i]);
    }
    return 0;
 }

bash进程把环境变量传递给env[];

其实系统在执行代码的时候,main函数并不是入口,而是_start函数,它会调用main函数,下面图中的arg_count表示main函数的参数个数

上面获取环境变量的这个代码是code.c文件,运行./code.c也就是bash的一个子进程,那么利用export添加环境变量之后,运行code.c文件,打印出来的env环境变量就会多出来自己添加的,也就是环境变量能够被子进程继承。

环境变量的全局性:就因为孙子也可以拿到环境变量

(7)通过系统调用获取或设置环境变量

cpp 复制代码
#include <stdlib.h>
 
int main()
 {
    printf("%s\n", getenv("PATH"));
    return 0;
 }

如果我想写一个程序,只让我自己zrx这个用户能跑,其他人都不行,那就用bash获取USER的环境变量,然后用if else语句去判断当前用户是不是zrx,是的话执行相应的代码,不是的话就不能执行,走else的这个语句。这也解释了为啥环境变量是全局的可以被子进程使用的,就是为了让子进程做自己想要做的特殊操作的。

(8)用char**environ获取环境变量,直接指向环境变量表

推荐法2

(9)本地变量

14、程序地址空间

实际是虚拟地址空间,并不是物理的地址空间,也叫做进程地址空间

有一份全局变量,子进程对其进行++,父进程不改,子进程读出来的数据在++,父进程不变,很正常,这是进程间独立的体现,有两份全局变量,但是从父进程和子进程去打印出来的变量的地址却是一模一样的,所以说明了这个地址是虚拟地址而不是物理地址。

解释:子进程继承了父进程的虚拟地址和页表,因此同一份虚拟地址在刚开始的时候映射到的物理地址是一样的,但是子进程要修改变量的话,就会发生写时拷贝,子进程页表虚拟地址对应的物理地址会发生改变,如下图所示,变成了223344,即给子进程新开了一个物理内存空间,共子进程去修改内容。

操作系统要对进程的虚拟地址空间做管理,也就是一个结构体,里面保存了每个空间的其实地址和结束地址

虚拟地址空间是一个结构体struct mm_struct

为什么需要有虚拟地址空间?

相关推荐
Eric.Lee20211 小时前
ubuntu系统在bashrc文件中对conda进行启用设置
linux·运维·python·ubuntu·conda
mooyuan天天1 小时前
CobaltStrike横向渗透之Https Beacon实战2(跳板机Linux)
linux·内网渗透·横向移动·cobalt strike
model20051 小时前
Alibaba linux 3安装mapserver
linux·运维·服务器
水天需0101 小时前
awk 命令全面详解
linux·运维·服务器
YFLICKERH1 小时前
【Linux系统】Docker技术与应用
linux·docker
dodod20121 小时前
在 Ubuntu 中将新硬盘挂载到 /home /work目录下
linux·运维·ubuntu
饕餮争锋1 小时前
Linux 常用命令分类详解
linux·运维·服务器
菜择贰1 小时前
为IDEA创建Linux桌面快捷方式
java·linux·intellij-idea
minji...1 小时前
Linux 进程控制(三) (进程程序替换,exec系列函数)
linux·运维·服务器