自学内容网 自学内容网

linux进程概念

冯洛伊曼结构

我们日常所用的计算机,如笔记本,和不常见的计算机,如服务器大部分都遵循冯洛伊曼体系。

截至目前,我们所了解的计算机都是有一个个硬件所组成。

输入单元:鼠标,键盘,扫描仪等。
中央处理器(CPU):含运算器和控制器等。
输出单元:显示器,打印机等。

不考虑缓存的情况下,CPU能且只能对内存(存储器)进行读写,不等访问外设(输入和输出设备)

同样外设也只能对内存进行读写,总结就是所有设备只能与内存打交道。

为什么要有内存呢?

由于 CPU 的处理速度远快于外设(如硬盘、网络接口、打印机等)的响应速度,直接让 CPU 等待外设完成任务会导致效率低下。为了避免这种情况,计算机系统中引入了缓存和其他缓冲机制。内存就是CPU和外设之间一个巨大的缓存!

操作系统(Operator System)

OS本质是一种对硬软件进行管理的程序!

为什么要有OS?

为了解决这个问题,我们需要了解为什么要设计操作系统。

根据上图我们可以看到,操作系统的设计是为了协调硬件和软件之间的关系,管理资源,提高计算机系统的效率和安全性。

总结:操作系统对下进行硬件资源管理,稳定的,高效的,安全的,进行良好的工作(手段)。
对上为用户提供一个安全的,搞笑的,稳定的运行环境(目的)。

操作系统如何管理(顶层理解)

我们举个栗子吧,就像是学校,管理者(比如校长,年级主任等),也有执行者(比如辅导员),还有被管理者(学生)。我们今天的身份是校长,如果我们如何管理学生呢,我们难道要一个一个的见面,然后告诉他们学校的规章制度以及对他们犯的错误进行惩处?当然不需要,我们只需要获取到他们每个人的信息以及一些必要数据,做一决策就好。那由谁来负责我们数据的收集呢,辅导员会对我们所需要的数据进行收集。但是到我们手里的数据会很杂乱,有很多我们并不需要的属性也会掺杂其中,为了解决这个问题,我们就想到能不能把我们所需要的数据属性“描述”一下,比如(学生的姓名,学号,性别等),然辅导员按照我们“描述的模板”进行数据收集,这时候对我们会很方便的进行管理。

操作系统(校长)对底层硬件的管理(学生),也是先描述,用struct结构体,通过驱动(辅导员)获取我们所需的数据,最后将这些获得数据以特定的结构组织管理起来,这就使得我们的操作系统很好的管理我们的软硬件资源。

系统调用和库函数概念

操作系统为什么会给我们提供系统调用接口以及库函数呢,直接让我们自己通过操作系统获取数据,它不香吗?

再举个栗子吧,银行使我们存取钱和办理其他业务的地方,银行里面有行长,办理业务的专员,柜台窗口等,假设我们直接开放银行,拆掉这些柜台,让用户自己办理业务。会发生什么呢?我们要知道群众里面是有坏人的,当我们完全开放后,里面的钱财会丢失,人员的安全也无法得到保障。
当我们不对外开放,而是有专门提供业务的人和窗口,需要办理什么业务只需要跟他们讲就好了。

带入我们的操作系统也是一样的,我们为其提供函数调用和库函数不仅可以防止有心之人的恶意破坏,还可以给用户提供更好的服务。

总结而言呢,统一接口,隐藏复杂性,进行权限管控和错误处理,拥有更好的安全性和稳定性,合理的进行资源分配以及跨平台的兼容和版本控制。

进程

什么叫进程?

基本概念

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

再简单来说,Windows通过双击,linux通过./执行的.exe文件,本质上就是开启一个进程。

直到用户退出才退出的常驻进程和执行完就结束的(如ls,pwd等指令)

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

理解CWD(Current Working Directory 即当前的工作目录)

为什么要了解这个呢?

首先我们要知道什么叫做CWD,它是指在文件系统中,用户或进程当前所在的目录。

我们可以通过pwd命令看到工作目录

进程属性  --  PCB

进程控制块(PCB,Process Control Block)是操作系统中用于管理进程的一个重要数据结构。它包含了一个进程在运行时所需的所有信息,帮助操作系统调度和控制进程的执行。每个进程都有一个对应的 PCB,通常包括以下内容:

  1. 进程状态:进程的当前状态(如运行、就绪、阻塞)。
  2. 程序计数器:指向进程下一条要执行的指令。
  3. CPU寄存器:存储进程在执行过程中使用的寄存器值。
  4. 内存管理信息:如进程的内存分配、页表等。
  5. 调度信息:包括优先级、调度队列等,用于进程调度。
  6. 进程标识符(PID):唯一标识进程的编号。
  7. I/O状态信息:指示进程使用的输入输出设备、I/O操作的状态。
  8. 父进程ID:如果该进程是子进程,还会保存父进程的ID。
  9. 等其他信息

操作系统通过维护每个进程的 PCB,能够实现多进程管理、进程调度和上下文切换等功能。

linux的PCB(task_struct)

描述进程-PCB

PCB是什么?

在linux系统中,进程控制模块PCB对应的数据结构是task_struct.

struct task_struct {
    volatile long state;             // 进程的状态
    unsigned int flags;              // 进程标志,包含进程的各种状态、条件等
    int pid;                         // 进程的 PID
    int tgid;                        // 线程组 ID(主进程的 PID)
    struct task_struct *parent;      // 父进程指针
    struct list_head children;       // 子进程链表
    struct list_head sibling;        // 同一父进程的兄弟进程
    struct mm_struct *mm;            // 进程的内存管理信息
    struct files_struct *files;      // 文件描述符表
    struct fs_struct *fs;            // 文件系统相关信息(当前工作目录、根目录等)
    struct sched_entity se;          // 调度实体
    struct thread_info *thread_info; // 线程信息(包括 CPU 上下文、栈等)
    unsigned long start_time;        // 进程的启动时间
    unsigned long real_start_time;   // 进程创建时的实际启动时间
    // ... (还有更多的成员)
};

以上是一些task_struct成员,task_struct是PCB的一种进程描述结构体。

为什么要有PCB

操作系统的作用是对硬件进行管理为软件提供服务,那么如何管理硬件呢?六个字“先描述,在组织”,PCB就是对硬件资源的描述来协助管理。

组织进程

在内核源码中,task_struct是以链表的形式存在内核中的。

查看进程

进程信息可以通过/proc文件夹进行查看

当然也可以使用用户级工具top和ps进行查看。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 while(1){
 sleep(1);
 }
 return 0;
}

通过系统调用获取进程提示符

进程(PID)getpid() 和 父进程(PPID)getppid()

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
 printf("pid: %d\n", getpid());
 printf("ppid: %d\n", getppid());
 return 0;
}

创建进程

系统调用fork创建进程

fork() 是一种系统调用,用于在类 UNIX 系统(如 Linux、macOS)中创建一个新的进程。它的基本语法如下:

#include <unistd.h> 

pid_t fork(void);
  • #include <unistd.h>fork() 是在 unistd.h 头文件中声明的,因此在使用 fork() 时需要包含这个头文件。

  • pid_t fork(void);

    • pid_t:这是一个整型数据类型,表示进程 ID(PID)。pid_t 是系统为进程 ID 定义的类型。
    • fork():该函数不接收任何参数,调用后会创建一个新的子进程,并返回两次。

返回值:

fork() 会根据不同的情况返回不同的值:

  • 成功创建子进程

    • 父进程:返回 子进程的 PID(正整数)。父进程继续执行。
    • 子进程:返回 0。子进程继续执行。
  • 创建失败

    • 返回 -1,并设置 errno 以指示错误原因。常见的失败原因包括系统资源不足或达到进程数限制等。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    pid_t pid = fork();  // 调用 fork() 创建子进程

    if (pid < 0) {
        // 如果 fork() 返回负值,表示创建子进程失败
        perror("fork failed");
        return 1;
    }

    if (pid == 0) {
        // 子进程执行的代码
        printf("This is the child process, pid = %d\n", getpid());
    } else {
        // 父进程执行的代码
        printf("This is the parent process, pid = %d, child pid = %d\n", getpid(), pid);
    }

    return 0;
}

到这个就有问题了?

fork为什么给子进程返回0,给父进程返回子进程pid?

1.代码分流:让父子进程自fork之后执行不同的代码
2.给子进程返回0表示创建成功,给父进程返回子进程pid,便于跟踪和控制子进程。

一个函数为什么会返回两次?

在fork函数return前,已经创建好了子进程。由于刚开始子进程并没有资源,操作系统不会立即为子进程分配完全独立的空间,而是通过共享页面(CR3寄存器存储的子进程的页面指针指向父进程的内存)与父进程进行资源共享。为了避免父子进程互相影响,操作系统通过页表将共享内存权限设置为只读权限。在子进程进行修改数据的时候进行写时拷贝(当父进程或者子进程尝试修改共享内存时,操作系统会创建一个新的内存页,只给修改方分配新的内存副本)。fork函数在执行完成后会修改子进程task_struct中的pid,进行写时拷贝,故而返回两个值。

fork创建子进程后,系统会多一个进程吗?

会,父子进程是两个独立的进程,有自己独立的PCB和代码数据。

父子进程谁先运行

有调度器决定。

进程状态

操作系统描述一个进程在其生命周期中所处的不同阶段,每个进程都会在不同的时间段处于不同状态,操作系统通过这些状态来调度和资源分配。

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 僵尸进程*/
};
R 运行态

一个程序只要放在CPU上就一直执行它?

并不是,在每一个进程中都有一个叫时间片的概念。

S 浅度睡眠

我们要知道我们有很多进程,我们的硬件资源(如 CPU、内存、I/O 设备等)是有限的,S状态是等待我们某种硬件资源就绪,就比如我们在调用‘scanf’时,如果我们通过键盘输入,该进程就是S

#include <stdio.h>

int main() {
  int num = 1;
  scanf("%d", &num);
  return 0;
}

D 深度睡眠

进程访问磁盘数据时,操作系统无法将起杀死。为什么呢?

举个栗子吧,有一个进程需要向磁盘写入1G数据,在这个时间段中,操作系统发现进程太多了,严重影响执行速度,这是发现“这个进程”,它“占着茅坑不拉屎”,一直在那等。操作系统决定干掉它。这个时候磁盘写入失败了,就去问进程,再次写入呢还是停止,但是发现这个进程被干掉了。得不到响应,它也不知道接下来该怎么办。

正是为了防止这种情况的发生。

T & t 暂停状态

t:调试暂停

T:可通过向进程发送SIGSTOP/SIGCONT信号使其进入停止状态和退出停止状态

死亡状态 X & 僵尸状态 Z

X:表示执行结束或者被干掉,通常指的是进程的终止状态

Z:

僵死状态( Zombies )是一个比较特殊的状态。当进程退出并且父进程(使用 wait() 系统调用
没有读取到子进程退出的返回代码时就会产生僵死 ( ) 进程
僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入 Z 状态
#include <stdio.h>
#include <stdlib.h>
int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 1;
 }
 else if(id > 0){ //parent
 printf("parent[%d] is sleeping...\n", getpid());
 sleep(30);/* 等待子进程退出*/
 }else{
 printf("child[%d] is begin Z...\n", getpid());
 sleep(5);
 exit(EXIT_SUCCESS);
 }
 return 0;
}

while :; do ps aux |head -1&&ps aux|grep 进程pid或名称|grep -v grep;echo "###############################################################";sleep 1;done

可通过以上命令循环查看。

进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎 么样了。可父进程如果一直不读取,那子进程就一直处于Z 状态?
是的!
维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在 task_struct(PCB) 中,换句话 说,Z 状态一直不退出, PCB 一直都要维护?
是的!
那一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?
是的!因为数据结构 对象本身就要占用内存,想想C 中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间

注:使用wait()或者waitpid()来读取子进程退出状态。

孤儿进程

父进程提前退出,子进程退出进入Z状态中,此子进程被称之为“孤儿进程”。孤儿进程会被1号init进程领养并回收!

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
 pid_t id = fork();
 if(id < 0){
 perror("fork");
 return 1;
 }
 else if(id == 0){//child
 printf("I am child, pid : %d\n", getpid());
 sleep(10);
 }else{//parent
 printf("I am parent, pid: %d\n", getpid());
 sleep(3);
 exit(0);
 }
 return 0;
}

为什么要被init进程领养?

因为子进程要退出,要被释放!

进程优先级

因为CPU资源有限,进程不止一个,所以必定存在竞争关系,操作系统为了保证良性竞争,确认了优先级。

cpu 资源分配的先后顺序,就是指进程的优先权( priority )。
优先权高的进程有优先执行权利。配置进程优先权对多任务环境的 linux 很有用,可以改善系统性能。
还可以把进程运行到指定的 CPU 上,这样一来,把不重要的进程安排到某个 CPU ,可以大大改善系统整体性能。
                                                用 ps -l可以查看到进程的优先级
PRI:表示这个进程被执行的优先级,其值越小越早执行
NI:表示这个进程的nice值

nice值表示进程可被执行的优先级的修正值。
PIR=PIR(old)+nice。
当nice为负值时,那么该进程的优先级值会变小,优先级会变高,进程越快被执行。

当然,nice也是有范围的,[-20, 19],共40个级别。

注:进程的nice值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。

查看优先级命令 -- top

修改nice值

进入top 按‘r’ -> 输入进程PID -> 输入nice值。

环境变量

查看linux环境变量_linux 环境变量-CSDN博客


原文地址:https://blog.csdn.net/HH_KZ1314/article/details/143151823

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!