目录

一、背景

二、操作系统

三、进程

1.进程的理解

2.进程task_struct本身内部的属性有哪些

1.启动

        2.进程创建的代码方式

1.pid 和 ppid(父进程与子进程)

2.而我们为什么要创建子进程?

四、进程状态

1.linux的进程状态

2.僵尸进程和孤儿进程

1.僵尸进程

2.孤儿进程

3.进程的阻塞、挂起和运行

五、进程优先级

1.什么是优先级

2.为什么需要优先级

3.Linux优先级的特点和查看方式

六、命令行参数和环境变量

1.命令行参数

2.环境变量

3.整体理解环境变量、系统和我们的程序结合

七、程序地址空间(进程的地址空间)

1.引入

2.理解

1.如何理解地址空间?

        1>什么是划分区域?

2.为什么要有地址空间?

3.如何进一步理解页表和写时拷贝

4.如何理解虚拟地址

八、实例:linux2.6内核进程调度队列


一、背景

冯.诺依曼体系结构

输入设备键盘、鼠标、摄像头、话筒、磁盘、网卡...
输出设备显示器、声卡、磁盘、网卡...
CPU运算器、控制器
存储器一般就是内存

        数据在计算机的体系结构进行流动,流动过程中,进行数据的加工处理,从一个设备到另一个设备,本质:这是一种拷贝!

        数据设备间拷贝的效率,决定了计算机整机的基本效率。

在硬件数据流动角度,在数据层面

1.CPU不和外设打交道,CPU和内存打交道。

2.外设(输入和输出)的数据,要先放到内存中

eg。程序运行,为什么要加载到内存?

二、操作系统

1.OS概念:

        操作系统是一个软硬件资源管理的软件。

广义上的认识:操作系统的内核 + 操作系统的外壳周边程序

狭义上的认识:只考虑操作系统的内核

2.结构示意图(简单示意图)

层状划分

3.尝试理解操作系统 --- "管理"

 不需要管理者和被管理者直接接触

 拿到数据才是目的,本质是对数据进行管理

任何管理

先描述,在组织

一个完整的操作系统包含对相关内容的管理和系统调用接口

为什么需要操作系统?

操作系统对下(手段)进行软硬件管理工作,对上层提供良好(高效、稳定、安全)的运行环境(目的)

三、进程

1.进程的理解

进程=内核task_struct结构体 + 程序代码和数据 (形象理解)

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

        linux下具体称呼为 struct task_struct { //linux进程控制块}

如何理解进程动态运行?

        只要我们的进程task_struct将来在不同的队列中,进程就可以访问不同的资源。

2.进程task_struct本身内部的属性有哪些

1.启动

        1>  ./XXXX ,本质时让系统创建进程并且运行 -- 我们自己写的代码形成的可执行 == 系统命令 == 可执行文件。在linux中运行的大部分执行操作,本质上都是运行进程!

        ps axj | head -1 && ps axj | grep 进程 :可用于查看当前进程状态并且加上标头

        2> 每一个进程 都有自己的唯一标识符,叫做进程pid (unsignen int pid)

        3> 一个进程想知道自己的pid? getpid函数

        4> ctrl+c在用户层面上终止程序,kill -9 pid,可以直接杀掉进程标识符为pid的进程

        2.进程创建的代码方式

1.pid 和 ppid(父进程与子进程)

                pid_t getppid(void) --- 获得当前进程父进程的pid

fork函数,fork之后,父子代码共享

        创建一个进程,本质上是系统多一个进程,多一个进程就是多个 1.内核task_struct 2.自己的代码和数据

        提问,父进程的代码和数据是从磁盘加载而来的,那子进程的代码和数据是从何而来的?

                默认是继承父进程的代码和数据

2.而我们为什么要创建子进程?

        答案是我们想让子进程跑跟父进程不一样的代码

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

int main()
{
  printf("process is running,only me! pid: %d\n", getpid());    
  sleep(3);    
  pid_t id = fork();     
  if(id == -1) return 1;    
  else if(id == 0)    
  {    
    //child    
    while(1)    
    {    
      printf("id = %d, i am child process! pid: %d ppid:%d\n", id, getpid(), getppid());        
      sleep(1);    
    }    
  }    
  else    
  {    
    //parent    
    while(1)    
    {    
      printf("id = %d, i am parent process! pid: %d ppid:%d\n", id,getpid(), getppid());        
      sleep(2);                                                                  
    }    
    
  }
  return 0;    
}

id = 0  =>  子进程执行

id = 子进程pid  => 父进程执行

杀掉父子进程任一进程都不会影响另一个进程的执行。不过另一个进程会跑到后台,这个后面再说

四、进程状态

1.linux的进程状态

struct tesk_struct
{
    //内部的一个属性
    int status;
};
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运行状态(running):并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。

S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))。

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.僵尸进程和孤儿进程

1.僵尸进程

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
    
int main()    
{    
  pid_t id = fork();    
  if(id == 0)    
  {    
    int cnt = 5;    
    while(cnt--)    
    {    
      printf("i am child process! cnt: %d pid: %d\n", cnt, getpid());
      sleep(1);    
    }    
  }    
  else    
  {    
    while(1)    
    {    
      printf("i am parent process! pid: %d\n", getpid());                
      sleep(1);    
    }    
  }    
  return 0;    
}  

运行示意图

Z --- 表示僵尸状态

在子进程执行完后,就变成了一个

僵尸进程:已经运行完毕,但是需要维持自己的退出信息,在自己的进程task_struct 会记录自己的退出信息,未来让父进程来进行读取。

        如果没有父进程读取,僵尸进程会一直存在!会引起内存泄漏问题

2.孤儿进程

修改一下代码,把cnt挪到父进程去,则

父进程如果先退出,子进程就会变成孤儿进程。孤儿进程一般都是会被1号进程(可看作OS本身)进行领养的。

孤儿进程为什么要被OS领养?因为依旧要保证子进程正常被回收。

我们直接在命令行启动的进程,它的父进程是bash,bash会自动回收新进程的Z

3.进程的阻塞、挂起和运行

让多个进程以切换的方式进行调度,在一个时间段内同时得以推进代码,就叫做并发

任何时刻,都同时有多个进程在同时运行,就叫做并行

阻塞态:

挂起态:

进程切换:

CPU内部的所有的寄存器中的临时数据,叫做进程的上下文。

进程在切换,最重要的一件事情就是:上下文数据的保护和恢复

CPU内的寄存器:

        寄存器本身是硬件,具有数据的存储能力,CPU的寄存器硬件只有一套

        CPU内部的数据,可以有多套,有几个进程,就有几套和该进程对应的上下文数据

                寄存器!=寄存器的内容

五、进程优先级

1.什么是优先级

指定进程获取某种资源的先后顺序

task_struct 进程控制块 ->  struct -> 内部字段 -> int prio = ???

linux中优先级数字越小,优先级越高

优先级:已经能,但是看顺序。  权限:能不能?的问题

2.为什么需要优先级

进程需要访问的资源(CPU)始终有限的,系统进程大都挺多的。

操作系统关于调度和优先级的原则:分时操作系统,基本的公平。如果进程因为长时间不被调度,就会造成饥饿问题

3.Linux优先级的特点和查看方式

PRI:进程优先级

NI:进程优先级的修正数据,nice值, 新的优先级 = 优先级 + nice ,达到对于进程优先级动态修改的过程。

注:1.nice值不能随意修改,它是有调整范围的。[-20,19]  40个数字,每次调整优先级,都是从80开始的。

修改方式:top->进入后按‘r’->输入pid->输入nice值

六、命令行参数和环境变量

1.命令行参数

int main(int argc, char* argv[])

这些参数可带可不带。

这些参数的意义:int argc, char* argv[]

先看现象

引出功能

 为什么需要命令行参数?

        本质,命令行参数本质是要交给我们程序不同的选型,用来定制不同的程序功能,命令中会携带很多的选项。

2.环境变量

为什么ls这种函数不需要带地址呢,而我们的process需要带上./?

linux中存在一些全局的设置,表明,告诉命令行解释器,应该去那些路径下寻找可执行程序

 系统中很多配置,在我们登录linux系统的时候,已经被加载到了bash进程中(内存)。

如何添加环境变量?

最开始的环境变量不是在内存中,而是在系统的对应的配置文件中。

查看所有的环境变量: env

自己导入环境变量: export name=value

       取消环境变量: unset name

本地变量

3.整体理解环境变量、系统和我们的程序结合

父进程的数据,默认能被子进程看到并访问。

bash进程启动时,默认会给子进程形成两张表

argv[]命令行参数表,environ[]环境变量表(从OS的配置文件来),bash通过各种方式交给子进程

环境变量具有系统级的全局属性,因为环境变量本身会被子进程继承下去

获取环境变量(代码级)

1.extern char **environ

2.通过main函数参数

3.getenv("PATH")

本地变量只在本bash内部有效,无法被子进程继承下去

导成环境变量。此时才能够被获取

echo export 为内建命令

七、程序地址空间(进程的地址空间)

1.引入

#include <stdio.h>    
#include <sys/types.h>    
#include <unistd.h>    
#include <string.h>    
#include <stdlib.h>    
    
int g_val = 100;    
    
int main()    
{    
  printf("fasther is running, pid = %d, ppid = %d, g_val = %d, &g_val = %p\n", getpid(), getppid(), g_val, &g_val);    
  pid_t id = fork();    
  while(1)    
  {    
    if(id == 0)    
    {    
      int cnt = 0;    
      while(1)    
      {    
        printf("i am a child process, pid = %d, ppid = %d, g_val = %d, &g_val = %p\n", getpid(), getppid(), g_val, &g_val);    
        sleep(1);    
        ++cnt;    
        if(cnt == 5)    
        {    
          g_val = 300;    
          printf("i am child process, g_val change %d -> %d\n",100,300);                                                                             
        }    
      }    
    }    
    else    
    {    
      while(1)    
      {    
        printf("i am a parent process, pid = %d, ppid = %d, g_val = %d, &g_val = %p\n", getpid(), getppid(), g_val, &g_val);    
        sleep(1);    
      }    
    }    
  }    
} 

父子进程具有独立性

注:这个地址绝不是物理地址,是虚拟地址

2.理解

子进程会把父进程的很多内核数据结构全拷贝一份

OS自主完成写时拷贝 --- 按需申请

独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。

通过调整拷贝的时间书匈奴,达到有效节省空间的目的

1.如何理解地址空间?

        1>什么是划分区域?

                地址空间结构体源代码

               地址空间本质是内核的一个struct结构体!内部很多属性都是表示start end的范围

2.为什么要有地址空间?

        1.将无序变成有序,让进程以统一的视角看待物理内存以及自己运行的各个区域!

        2.进程管理模块和内存管理模块进行解耦。

        3.拦截非法请求 -> 对物理内存进行保护     

3.如何进一步理解页表和写时拷贝

        OS识别错误时:1.是不是数据不在物理内存 -> 缺页中断

                                   2.是不是数据需要写时拷贝 -> 发生写时拷贝

                                   3.如果都不是,才会进行异常处理。

4.如何理解虚拟地址

        虚拟地址空间 -> 页表 -> 物理地址

        提问:在最开始的时候,地址空间页表里面的数据从何而来?

                程序里面本身就有地址  --- 虚拟地址(逻辑地址)

int main()    
{    
  pid_t id = fork();    
    if(id == 0)    
    {    
      while(1)    
      {    
        printf("i am a child process id:%d, &id:%p\n",id,&id);    
        sleep(1);    
      }    
    }    
    else if(id > 0)    
    {    
      while(1)    
      {    
        printf("i am a parent process id:%d, &id:%p\n",id,&id);    
        sleep(1);                                                                                                                                    
      }    
    }    
  return 0;    
} 

fork() 之后需要return,return 的本质是对id进行写入,发生写时拷贝,虚拟地址相同,但物理地址不同

八、实例:linux2.6内核进程调度队列

进程调度大O(1)算法

两个array数组结构体,一个结构体只出不进,用actice指向。一个结构体只进不出,用expired指向。bitmap是位图,只需5次便可扫描出queue中是否还有数据。queue则管理各种优先级的进程,[100,139].arrive指向的结构体清空后,则交换active和expired两个指针。从而交替进行。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐