Linux进程

Linux进程


先描述再组织一个很重要的概念:C++加上STL = 描述 + 组织

进程是一种系统进行管理的方式:实质上进程就是用语言:类:来描述进程对象带有的属性,用链表来建立各个进程的联系,进行统一的管理(增删查改)

日常开发操作我们都是使用库(lib)里实现好的接口(C标准库,C++标准库)来调用操作系统的接口进行systemcall

1.进程概论

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

进程的产生与消亡

造成进程产生的主要事件有:

  • 系统初始化
  • 执行进程创立程序
  • 用户请求创立新进程

造成进程消亡的主要事件:

  • 进程运行完成而退出。
  • 进程因错误而自行退出
  • 进程被其他进程所终止
  • 进程因异常而被强行终结

2.进程管理-PCB

程序信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合(一个进程就有一个PCB)

PCB:struct task_struct

进程管理所需要的手段

当一个进程产生时,操作系统需要为其创建记录。操作系统用于维护进程记录的结构:进程控制块PCB,不同的操作系统维护的进程记录不同。但一般来说,应当包括寄存器、程序计数器、状态字、栈指针、优先级、进程ID、信号、创建时间、所耗CPU时间、当前持有的各种句柄等。而采纳的数据结构主要是线性表、链表和结构体,当然也可能使用树和图结构。
在这里插入图片描述

每一个进程都有一个自己的标识符:PID


查看进程:

ps axj 
ps aux
ps axj | grep .... //筛选
ps axj | head .... //筛选

获取进程PID的方法:

GETPID(2)                                            Linux Programmer's Manual                                            GETPID(2)

NAME
       getpid, getppid - get process identification

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       pid_t getpid(void);
       pid_t getppid(void);

DESCRIPTION
       getpid()  returns  the  process  ID  of the calling process.  (This is often used by routines that generate unique temporary
       filenames.)

       getppid() returns the process ID of the parent of the calling process.

杀掉进程:

ctrl + c //在用户层面终止进程
kill -9 PID //直接杀掉进程

通过系统调用创建进程:

fork()

#include<stdio.h>
#include<unistd.h>
int main()
{
    printf("i am father process : %d\n",getpid());
    pid_t ret = fork();// 创建一个新的进程
    if(ret == 0)
    {
        //child
        while(1)
        {
        	printf("i am child process :,pid:%d,ppid:%d\n",getpid(),getppid());
        }
    }
    else if(ret>0)
    {
        //father
        while(1)
        {
       		printf("i am father process :,pid:%d,ppid:%d\n",getpid(),getppid());
        }
    }
    return 0;
}
功能创建一个新的进程
头文件#include <unistd.h>
原型pid_t fork(void);
返回值成功: 0 或者大于 0 的正整数,失败:-1
备注该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为0 ,在原来的父进程中其返回值为大于 0 的正整数,该正整数就是子进程的PID

fork可以用来创建一个子进程,一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值,而父进程中返回子进程ID

父子进程共享用户代码,而用户数据各自私有一份,因为用户代码是只读的,不可以修改的,所以共享用户代码不影响,而为什么用户数据各自私有一份?因为在操作系统中,所有进程具有独立性,这是操作系统表现出来的特性,如何做到进程互相干扰?就让用户数据各自私有一份,这样进程就不互相干扰了

3.进程状态

进程状态类似于我们在IDE中对代码进行打断点调试,就是改变进程的状态

Linux进程状态:

kill -l改变进程状态:主要注意9,18,19

是否带有 + 号表示前台和后台进程

  • R:进程运行的状态
  • S:休眠状态,进程在等待“资源”就绪(可中断睡眠)可被kill
  • T:进程暂停状态,等待进一步唤醒
  • D:休眠状态,进程在等待“资源”就绪(不可中断睡眠 )不可被kill
  • X:死亡状态,这个状态只是一个返回状态,你不会在任务列表里看到这个状态

加深理解:

S:由于进程未获得它所申请的资源而处在等待状态。一旦资源有效或者有唤醒信号,进程会立即结束等待而进入就绪状态

D:进程也处于等待资源状态。一旦资源有效,进程会立即进入就绪状态。这个等待状态与可中断等待状态的区别在于:不能被信号量或者中断所唤醒,只有当它申请的资源有效时才能被唤醒。

这个状态被应用在内核中某些场景中,比如当进程需要对磁盘进行读写,而此刻正在DMA中进行着数据到内存的拷贝,如果这时进程休眠被打断(比如强制退出信号)那么很可能会出现问题,所以这时进程就会处于不可被打断的状态下。

4.僵尸进程(存在内存泄漏问题)

Z:僵尸状态,进程已经运行完毕,但是要维护自己的退出信息,会在自己的PCB结构体中记录退出信息,未来让父进程读取,否则就会一直挂起占用内存

进程因某种原因而中止运行,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外,并且系统对它不再予以理睬,所以这种状态也叫做“僵死状态”,进程成为僵尸进程。

僵尸进程无法被kill

5.孤儿进程

孤儿进程是指父进程退出或终止后,仍然在运行的子进程。孤儿进程会被init进程(进程号为1)收养,由init进程负责收集它们的状态信息,避免它们成为僵尸进程。孤儿进程不会对系统造成危害,因为它们不会占用进程表的资源。

6.进程优先级

基本概念:

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

ps -l or ps -al查看系统进程:

在这里插入图片描述

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

PRI 和 NI:
PRI:即进程的优先级,用于判断CPU执行进程的先后顺序,值越小进程的优先级别越高
NI:又被称为nice值,其表示进程可被执行的优先级的修正数值
PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice
因此调整进程优先级,在Linux下,就是调整进程,nice的取值范围是-20至19,一共40个级别

nice取值受限制目的是为了防止用户修改进程优先级过度导致进程饥饿现象,这样一来才能保证进程的并发运行

top查看全部进程

用top命令更改已存在进程的nice值:

在这里插入图片描述

其他概念

  • 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  • 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
  • 并行: 多个进程在多个CPU下分别,同时进行运行
  • 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进

7.环境变量和命令行参数和本地变量

环境变量默认在配置文件中

每当我们启动shell的时候环境变量会从配置文件加载到bash(内存中)

常见环境变量

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

和环境变量相关的命令

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

echo示例:
在这里插入图片描述


命令行参数

int main(int argc, char* argv[],char* env[])//定制程序功能
{
    int i = 0;
	for(i = 0; env[i]; i++){
	printf("env[%d]->%s\n", i,env[i]);
	return 0;
}

在Linux中,命令行参数和环境变量是两种不同的方式来传递信息给程序。命令行参数通常在执行命令时直接跟在命令名后面,而环境变量则是在操作系统中定义的,可以被运行在该环境下的所有程序访问


本地变量

在Linux中,本地变量通常指的是在Shell脚本或命令行中定义的变量,它们只在当前Shell会话中有效。本地变量不会被系统中运行的其他程序或脚本所访问,除非它们被显式地传递给其他Shell实例

例如,如果你在命令行中定义了一个变量:

my_variable="Hello"

这个my_variable就是一个本地变量。你可以通过echo $my_variable来访问它的值,但是一旦你关闭了这个Shell会话,my_variable变量就会消失,不会留在系统中

命令行参数:

  • 用于向程序传递参数或配置选项
  • 例如,在使用ls命令时,-l是一个命令行参数,表示以长格式列出文件信息

环境变量:

  • 是定义在操作系统层面的变量,可以被运行在该系统上的所有程序访问
  • 例如,PATH环境变量定义了系统搜索可执行文件的目录

要设置或修改环境变量,可以使用export命令。例如,要将/usr/local/bin添加到PATH变量中,可以使用以下命令:

export PATH=$PATH:/usr/local/bin

这会在当前会话中生效。要永久设置环境变量,需要将类似的命令添加到用户的~/.bashrc~/.bash_profile文件中

8.程序地址空间(进程)

在Linux中,程序的地址空间是由虚拟内存管理的,它包括了多个不同的段,每个段都有特定的用途和属性。这些段通常包括:

  • 程序段(Text):包含了程序的机器代码,通常是只读的,以防止程序在运行时被修改
  • 初始化数据段(Data):包含了程序中已初始化的全局变量和静态变量
  • 未初始化数据段(BSS):包含了程序中未初始化的全局变量和静态变量,这些变量在程序启动时被初始化零
  • 堆(Heap):用于动态内存分配,程序运行时可以通过如mallocnew等函数分配内存
  • 栈(Stack):用于存储局部变量和函数调用的上下文信息,如返回地址和参数

在这里插入图片描述

进程地址空间究竟是什么?

下面我们通过一个代码来看一个现象,我们定义了一个全局变量,g_val fork()创建一个子进程,让父进程和子进程完成自己的任务,在子进程中定义count来计数,当子进程的打印任务进行到第五次时,让子进程将这个全局变量改成0:

#include <stdio.h>
#include <unistd.h>
int g_val = 100;
int main()
{
    printf("begin.....%d\n", g_val);
    pid_t id = fork();
    if (id == 0)
    {
        // child
        int count = 0;
        while (1)
        {
            printf("child: pid: %d,ppid: %d, g_val:%d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
            count++;
            if (count == 5)
            {
                g_val = 0;
            }
        }
    }
    else if (id > 0)
    {
        // father
        while (1)
        {
            printf("father: pod: %d,ppid: %d, g_val:%d, &g_val: %p\n", getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }
    }
    else
    {
        // todo
    }
    return 0;
}

在这里插入图片描述

1.地址空间和物理内存之间的关系

父子进程在同一个地址处读取数据怎么会是不同的值呢?解释:我们曾经所学到的所有的地址,绝对不是真实的物理地址

在Linux地址下,我们所能看到的地址叫做虚拟地址,我们在用C/C++语言所看到的地址,全部都是虚拟地址,物理地址用户是无法看到的,由操作系统统一管理物理地址

为什么会存在进程地址空间呢?

父子进程是共享数据的,但由于进程是存在独立性的,因此,每一个程序(进程)拥有一个程序地址空间,便于进程的独立写入不改变其他进程的数据

总结:虚拟内存地址通过页表映射到实际的物理内存中!OS必须负责将虚拟地址转化成物理地址

那么如何划分进程地址空间的区域呢?在Linux当中,进程地址空间本质上是一种数据结构,是多个区域的集合。

在Linux内核中,有这样一个结构体:struct mm_struct,在这个结构体来表示我们说的一个一个的进程地址空间的不同区域

struct mm_struct
{
    unsigned long code_start;//代码区
    unsigned long code_end;
    
    unsigned long init_start;//初始化区
    unsigned long init_end;
    
    unsigned long uninit_start;//未初始化区
    unsigned long uninit_end;
    
    unsigned long heap_start;//堆区
    unsigned long heap_end;
    
    unsigned long stack_start;//栈区
    unsigned long stack_end;
    //......
}

2.为什么要存在地址空间?

为什么进程不直接访问物理内存呢?这样不行吗?为什么要存在地址空间呢?

  1. 保护物理内存不受到任何进程内地址的直接访问,在虚拟地址到物理地址的转化过程中方便进行合法性校验
  2. 将内存管理和进程管理进行解耦(实际体现在效率提高,解放空闲内存)
  3. 让每个进程,以同样的方式(虚拟地址),看待代码和数据,明确程序运行的地址

*这就是为什么父子进程数据可能是不一样的:*因为数据是私有的,但是地址却也是相同的

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jamo@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值