进程概念相关

目录

1、冯诺依曼体系结构

2、操作系统概念与定位

2.1 什么是操作系统?

2.2操作系统是干什么的?

2.3操作系统是如何完成这些事情的?

2.4操作系统组织结构图

2.5系统调用&库函数

3、进程概念

3.1 什么是程序?什么是进程?

3.2操作系统是如何管理进程的?

3.3描述(PCB)

3.3.1进程号(PID)(进程标识符)

 3.3.2 进程状态:就绪/运行/阻塞

3.3.3细分的进程状态:

3.3.4  程序计数器:保存程序下一条执行的指令(为下一次进程执行做准备工作)

3.3.5  上下文信息:保存寄存器当中的内容

3.3.6   内存指针:指向"程序地址空间"

3.3.7  记账信息:使用CPU时长,占用内存大小

3.3.8   IO信息:保存进程打开文件的信息

3.4 组织

3.5.创建子进程

3.5.1    fork函数

 3.5.2  原理:

 3.5.3  既然父子进程的代码是相同的,那么子进程是从哪行代码开始执行的?

 3.5.4   扩展:正常在命令行当中启动的进程,他的父进程是谁呢?

3.6 僵尸进程&僵尸状态

3.6.1 僵尸进程的产生:子进程先于父进程退出,子进程就会变成僵尸进程;

 3.6.2 原因:子进程在退出的时候,会告知父进程(信号:后续会涉及到),父进程忽略处理,父进程并没有回收子进程的退出状态信息。

3.6.3 僵尸进程的危害:

3.6.4僵尸进程的解决方案:

3.7 孤儿进程

3.7.1.孤儿进程的产生:父进程先于子进程退出,子进程就会变成孤儿进程。

 3.7.2.原因:

3.7.3.孤儿进程的退出信息由1号进程回收。

3.7.4.孤儿进程没有危害。


1、冯诺依曼体系结构

我们日常生活中,计算机,服务器,大部分都遵循冯诺依曼体系。

截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
输入单元:包括键盘, 鼠标,扫描仪, 写板等。
中央处理器(CPU):含有运算器控制器等,进行算术运算和逻辑运算(本质上就是高低电平通过与、或、非门)。
存储器:内存(并非磁盘)。
输出单元:显示器,打印机等。

冯诺依曼的两个重要思想:

1、所有的数据都采用二进制进行存储,即计算机处理的数据都是二进制;2、运算产生的数据都是存储在内存当中。

2、操作系统概念与定位

2.1 什么是操作系统?

操作系统 = 操作系统内核 + 一堆应用

2.2操作系统是干什么的?

操作系统在管理计算机的软硬件资源。
硬件资源:CPU、内存、磁盘、网卡、显示器等。
软件资源:进程资源,驱动程序。

2.3操作系统是如何完成这些事情的?

通过管理,管理 = 描述(结构体)+ 组织(串联结构体)

2.4操作系统组织结构图

2.5系统调用&库函数

系统调用:操作系统内核提供的函数,被称为系统调用函数。
库函数:c标准库提供给的函数,库函数的代码实现当中调用了系统调用函数
库函数与系统调用函数的联系:由于某些系统调用函数参数比较生僻,对程序员的要求比较高,因此,一些大神级别的程序员将系统调用函数进行封装,再提供给其他程序员。

3、进程概念

3.1 什么是程序?什么是进程?

程序:源代码经过编译产生的可执行文件,这个文件是静态的。
进程:程序运行起来的实例,是动态在运行的(运行程序定义的代码)。

3.2操作系统是如何管理进程的?

进程的管理 = 描述(PCB)+ 组织方式(链表)

 PCB:process ctrl block(进程控制块),操作系统代码当中的一个结构体

3.3描述(PCB)

task_struct {
进程号(PID)
进程状态:就绪/运行/阻塞
细分的进程状态:
  R:运行状态
  S:可中断睡眠状态
  D:不可中断唾眠状态 
  T:暂停状态(ctr1+z)
  t:跟踪状态
  X:死亡状态
  Z:僵尸状态(重点)
程序计数器:保存程序下一条执行的指令
上下文信息:保存寄存器当中的内容
内存指针:指向"程序地址空间"
记账信息:使用CPU时长,占用内存大小
IO信息:保存进程打开文件的信息
}

进程的信息可以通过 /proc 系统文件夹查看

 如:要获取PID为1的进程信息,你需要查看 /proc/1 这个文件夹。大多数进程信息同样可以使用top和ps这些用户级工具来获取。

3.3.1进程号(PID)(进程标识符)

作用:标识当前在操作系统当中的一个进程。在一个操作系统中,一个进程拥有的进程号是唯一的。进程号可以重复使用。

在linux操作系统下查看进程号

ps aux:查看当前操作系统中所有进程的信息

ps -ef

 如果要看自己进程的PID,那么一定要让进程处于持续运行的状态。

ps aux | grep [可执行程序]

系统调用函数查看进程号

pid_t getpid(void);

1.功能:谁调用就获取谁的PID(进程号);2.pid_t :内核定义的进程号的类型,这个类型本质上就是整型(int)  ,该函数没有参数;3.返回值:返回当前调用“getpid”函数的进程的进程号

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

int main(){
    while(1){
        printf("pid:%d\n",getpid());
        sleep(1);
    }
    return 0;
}

通过ps aux | grep pid_test查看

 man  getpid,查看2号手册

 3.3.2 进程状态:就绪/运行/阻塞

运行:进程占用CPU,并在CPU上运行;理解为进程正在使用cpu来执行自己的代码。
就绪:进程已经具备运行条件,但是CPU还没有分配过来;理解为进程已经将运行前的准备工作全部做好了,就等着操作系统调用,占用CPU了。
阻塞:进程因等待某件事发生而暂时不能运行;例如等待IO输入,(调用某些阻塞接口)。

三个状态间的关系:

程序运行结束有可能处于就绪状态等等(当前进程的时间片一到,或者是其他进程的优先级高),也有可能处于阻塞状态(进程需要等待某些资源了)

进程是抢占式执行
    计算机的CPU数量少,进程数量多(常态〉,操作系统在调度的时候要做到雨露均沾,让每一个进程都能运行上,但是操作系统在调度的时候,是从就绪队列当中获取进程,进行运行。换句话说,哪个进程准备好了,就绪了,原则上就可以被调度。所以,进程为了能够执行自己的代码,都是抢占式执行,不会互相谦让。
     所以进程就会有不同的状态,其中之一原因就是“狼多肉少”。
    

   操作系统调度的时候有各种算法:先来先服务,短作业优先,高优先级优先,时间片(毫秒级别:5-8O0)轮转等等。

先来先服务:即先来后到,哪个进程先到,就先执行谁。
短作业优先:运行时间短的进程现被调度。
高优先级优先:进程优先级越高,被调度的概率就越高。
时间片轮转:给每一个就绪状态的进程在分配CPU资源的时候,分配一个时间片,在时间片时间内,进程可以拿着CPU进行运算自己的代码。时间片到了,就会被剥离CPU资源。

    并发:多个进程在一个CPU下,采用进程切换的方式,各自独占CPU运行各自的代码,交替运行,让多个进程都得以推进,称之为并发。
     并行:多个进程在多个CPU下,同时运行各自的代码,称之为并行。

3.3.3细分的进程状态:


 

3.3.3.1   R:运行状态:处于R状态的进程,有可能在执行代码,有可能在运行队列(就绪队列),在就绪队列+运行状态的进程,STAT都是R。

#include <stdio.h>

int main(){
    while(1){
        
    }
    return 0;
}


 

  3.3.3.2   S:可中断睡眠状态:进程正在睡眠(被阻塞),等待资源到来被唤醒,也可以通过其他进程信号或时钟中断唤醒,进入运行队列。

sleep函数:


 

 CPU使用时长为0的原因:当前进程使用CPU的时间极短,无法用秒这个量级来表示。

3.3.3.3    D:不可中断唾眠状态 :通常等待一个I0结束(也就是输入输出结束)。


3.3.3.4    T:暂停状态(ctr1+z):结论:处于暂停状态的进程,没有退出,进程是存在的,而是暂时不执行我们程序员定义的代码。(中场休息)

ctrl+z:暂停一个进程。

 暂停一个进程之后,可以用fg命令让进程继续运行


 

3.3.3.5     t:跟踪状态:调试程序的时候可以看到

执行gdb pid_test命令后,pid_test程序并没有运行,运行的是gdb程序

在gdb程序中执行r命令后,pid_test程序才被执行

 gdb启动pid_test程序,给的是一个绝对路径:/home/sy/study/class_linux/pid_test/pid_test

3.3.3.6     X:死亡状态:这个状态是用户看不到的,在PCB被内核释放的时候,进程会被置为x,紧接着进程就退出了。
3.3.3.7     Z:僵尸状态

3.3.4  程序计数器:保存程序下一条执行的指令(为下一次进程执行做准备工作

进程切换:进程从CPU当中剥离出来,操作系统将CPU资源让其他进程使用。
在进程切换出去的时候要保存现场:进程目前执行到哪里,
程序计数器将即将执行什么指令保存好
恢复现场:通过程序计数器当中的指令,来获取当前进程下一步要执行的代码。

指令是指:汇编代码(指令)

#include <stdio.h>
#include <unistd.h>
 
   
int main(){
  int a=2;
  int b=888;                                                                                                                                                    
  while(1){
  printf("hello!\n");
   sleep(1);
  }
  return 0;
}

程序目前要执行的指令是 movl   $ox378,-0x8(%rbp)

假如当前进程被切换,则程序计数器中保存的就是类似于movl   $ox378,-0x8(%rbp)的汇编指令

ni:按照汇编语言进行单步调试

3.3.5  上下文信息:保存寄存器当中的内容

硬件设备的发展,导致不通用硬件之间的读取速度不一致,因此需要缓存,寄存器。

奇存器并不是进程独有的内容,不属于进程,缓存也不属于进程,寄存器是属于操作系统的;CPU获取的数据是从寄存器当中拿。

3.3.6   内存指针:指向"程序地址空间"

3.3.7  记账信息:使用CPU时长,占用内存大小

3.3.8   IO信息:保存进程打开文件的信息

每一个进程被创建的时候,都会默认打开三个文件
stdin(标准输入): scanf getchar
stdout(标准输出): printf
strerr(标准错误): perror

一个进程被创建出来之后,操作系统会以进程的进程号命名一个文件夹,该文件夹下的内容都是该进程的相关的内容。
/proc :都被放到这个目录下了

 软连接文件相当于windows操作系统中的快捷方式。

3.4 组织

3.5.创建子进程

3.5.1    fork函数

1.fork :创建出来一个子进程,头文件:#include <unistd.h>后续系统调用函数都需要包含该头文件,2.fork()的返回值,创建成功,fork会返回两次,在父进程当中返回一次,其值>0(子进程的进程号),在创建的子进程当中返回一次,其值 ==0。创建失败,返回-1

 谁调用fork函数,成功之后,谁就创建出来一个子进程。A进程代码调用fork,创建成功之后,A进程就创建出来一个子进程a,A(父进程)-->a(子进程)。

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

/*1.创建子进程
 *2.父子进程分别打印各自的进程号
* */
 int main(){
    pid_t ret=fork();
    printf("ret:%d,pid:%d,ppid:%d\n",ret,getpid(),getppid());
    return 0;                                                                                                                                                       
 }

 一旦父进程将子进程创建出来,父子进程也是抢占式执行,并没有规定,一定要让谁先运行。一定是父进程先运行且结束了,导致子进程变成孤儿进程,孤儿进程的父进程是1号进程(因为孤儿进程被1号进程领养了)。

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

//让程序睡上一秒
 int main(){
     pid_t ret=fork();
     printf("ret:%d,pid:%d,ppid:%d\n",ret,getpid(),getppid());
     sleep(1);                                                                                                                                                       
     return 0;
}

 3.5.2  原理:

1.子进程拷贝父进程的PCB;2.父子进程代码共享(父子进程拥有的代码是一样的);3.数据独有(各自有各自的进程地址空间)。

进程独立性:多个进程运行,需要独享各种资源,各自拥有自己的进程地址空间,互相在执行的时候,数据不会窜,互不干扰。

 3.5.3  既然父子进程的代码是相同的,那么子进程是从哪行代码开始执行的?

从fork之后开始运行

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

int main(){
    printf("beginning of process,pid:%d\n",getpid());//子进程不能打印
    pid_t ret=fork();
    printf("ret:%d,pid:%d,ppid:%d\n",ret,getpid(),getppid());
    sleep(1);
    return 0;
}

 可以通过分支语句让父子进程执行不同的代码块;

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

int main(){
    pid_t ret=fork();
    if(ret<0){
        //子进程创建失败
        return -1;
    }
    else if(ret == 0){
        //子进程执行 
        printf("i am child!pid:%d ppid:%d\n",getpid(),getppid());
    }
    else{
        //父进程执行
        printf("i am father! pid:%d ppid:%d\n",getpid(),getppid());
    }
    sleep(1);
    return 0;
}

父子进程的代码,都是由if分支语句的。但是在执行的时候:父进程执行>0 的代码块
子进程执行== 0的代码块

 父子进程执行完各自的代码块后,如果进程没有退出,就会按照代码的顺序望下去执行

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

int main(){
    pid_t ret=fork();
    if(ret<0){
        //子进程创建失败
        return -1;
    }
    else if(ret == 0){
        //子进程执行 
        printf("i am child!pid:%d ppid:%d\n",getpid(),getppid());
    }
    else{
        //父进程执行
        printf("i am father! pid:%d ppid:%d\n",getpid(),getppid());
    }
    printf("hello! pid:%d\n",getpid());
    sleep(1);
    return 0;
}

 3.5.4   扩展:正常在命令行当中启动的进程,他的父进程是谁呢?

bash

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

int main(){
    pid_t ret=fork();
    if(ret<0){
        //子进程创建失败
        return -1;
    }
    else if(ret == 0){
        //子进程执行 
        printf("i am child!pid:%d ppid:%d\n",getpid(),getppid());
    }
    else{
        //父进程执行
        printf("i am father! pid:%d ppid:%d\n",getpid(),getppid());
    }
    printf("hello! pid:%d\n",getpid());
    //让进程不退出
    while(1){
    sleep(1);
    }
    return 0;
}

 我们在终端中启动的进程,都是bash创建出来的

 ./pid_test就是让bash知道当前要执行的程序在哪里(在当前目录下)

3.6 僵尸进程&僵尸状态

3.6.1 僵尸进程的产生:子进程先于父进程退出,子进程就会变成僵尸进程;

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

int main(){
   pid_t ret=fork();
   if(ret<0){
    return -1; 
   }
   else if(ret == 0){
       //子进程创建成功,子进程执行
       printf("I am child process:%d ppid=%d\n",getpid(),getppid());
   }
   else{
       //父进程执行
       while(1){
       printf("I am parent process:%d ppid=%d\n",getpid(),getppid());
       sleep(1);
       }
   }
    return 0;
}

 3.6.2 原因:子进程在退出的时候,会告知父进程(信号:后续会涉及到),父进程忽略处理,父进程并没有回收子进程的退出状态信息。
 

1.子进程退出的时候,会告诉父进程;
2.父进程也收到了来自子进程的通知;
3.父进程是忽略处理(不处理);
4.导致子进程的退出状态信息,一直没有得到回收所以,子进程在内核当中的PCB就一直没有释放;
6.从而就看到了子进程变成了僵尸进程
(子进程的状态就是僵尸状态:Z)

3.6.3 僵尸进程的危害:

子进程的PCB得不到释放,造成了内存的泄露

扩展:ki11命令:可以终止一个进程  
kill [pid] :普通终止
kil1 -9 [pid]:强杀

程序员针对僵尸进程,使用ki11命令是不能够杀死的。

一个系统当中如果僵尸太多,浪费的内存就越大。

3.6.4僵尸进程的解决方案:
 

杀死父进程(不推荐)
重启操作系统:(不推荐)
父讲程讲行进程等待

3.7 孤儿进程

3.7.1.孤儿进程的产生:父进程先于子进程退出,子进程就会变成孤儿进程。

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

/*1.创建子进程
 *2.子进程不退出,父进程退出 
 */
int main(){
    pid_t ret=fork();
    if(ret < 0){
        //创建失败
        return -1;
    }
    else if( ret == 0 ){
        //child progress
        while(1){
        sleep(1);
        printf("i am child ,ppid:%d\n",getppid());
        }
    }
    else{
        // father progress
        sleep(1);
        printf("i am father pid:%d\n",getpid());
    }
    return 0;
}

 

 3.7.2.原因:

父进程先于子进程退出之后,既定回收子进程的父进程不在了,所以子进程就变成孤儿程,所以称之为孤儿进程

3.7.3.孤儿进程的退出信息由1号进程回收。

3.7.4.孤儿进程没有危害

注意:没有孤儿状态!!!

扩展;1.什么是1号进程:/usr/lib/systemd/systemd :操作系统启动的第一个进程,后续有很多进程都是由该进程创建出来的,或者是由该进程的子孙创建出来的;1号进程做了操作系统初始化的一些工作,操作系统就是一个软件(一堆代码)。

2.

 R+:表示当前进程是一个前台进程,特性:阻塞bash, 让bash不能处理其他问题;没有了+,当前的进程就变成了后台进程,特性:不会阻塞启动的bash,默默的在后端运行。

猜你喜欢

转载自blog.csdn.net/sy2453/article/details/123219140