Linux进程

冯诺依曼体系结构

我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。

Linux进程_第1张图片
对于运算器和控制器都是属于CPU的,CPU处理数据的速度是非常快的,其次是内存,接下来才是各种外设。这里的存储器其实就是内存了。
离CPU越近,速度越快,价格越贵,存储效率越高。

  1. 输入单元:包括键盘, 鼠标,扫描仪, 写板等
  2. 中央处理器(CPU):含有运算器和控制器等
  3. 输出单元:显示器,打印机等。

不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备),因为外设的速度太慢了,如果CPU直接和外设打交道,会让CPU速度下降很多。
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。也就是说,所有的设备都只能直接和内存打交道。

正是因为我们的计算机都遵循冯诺依曼体系结构,CPU和外设都只和内存打交道,通过内存来进行各种数据的传递和处理,所以才有了今天我们大家都能用的起的笔记本电脑以及各种设备。

我们知道可执行程序在运行前要加载到内存,那这是为什么呢?

我们的程序 = 代码 + 数据,都要有CPU来进行处理,但是CPU只和内存进行交互,我们的可执行程序是.exe,本质是二进制文件,文件就存在磁盘上,该文件想要被CPU执行就要加载到内存中。

操作系统

什么是操作系统?

操作系统是一款软件,是一款进行软硬件资源管理的软件。在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件

为什么要有操作系统呢?

因为我们用户是不擅长和硬件打交道的,操作系统能够把软硬件资源管理好,给用户提供良好(稳定,高效,易用,安全)的使用环境。

既然操作系统是一款搞管理的软件,那怎么管理呢?

管理一个对象的本质是管理该对象的各种属性,就比如在学校校长需要管理我们学校这么多人,他肯定不可能所有人都认识,但是他只要有我们每个人的信息(名字,学号等)就可以把我们给管理起来,操作系统也是这样,它需要管理硬件,但是他不直接和硬件打交道,他只要有各种硬件的各种信息就可以把他们给管理起来。
操作系统大部分都是用C语言写的,他想要管理各种外设的各种属性就一定会使用struct结构体把他们给描述出来,然后通过各种数据结构把他们个管理起来,这样就可以完成对他们的属性的管理,也就完成了对他们的管理。我们把这种管理总结成6个字,先描述,后组织

我们看一下操作系统的层次结构。

Linux进程_第2张图片

操作系统管理的核心就是对内存管理,对进程管理,对文件管理,对驱动管理。

我们现在可以理解操作系统如何管理各种硬件了,当然和硬件中间还有驱动程序,每种硬件基本上都要有对应的驱动程序,不然就无法正常工作,那么系统调用接口是什么呢?

我们可以保证自己在使用操作系统是不对他进行破坏或非法操作,但是群众里面有坏人,我们不能保证所有人都不做坏事,所以操作系统直接统一不让所有人访问,但是他还要为我们提供服务,因此就有了系统调用,系统调用是写操作系统的人写的,一定不会对操作系统进行非法操作什么的,所以操作系统以系统调用的方式为我们提供各种接口,来供我们程序员使用。

但是又存在一个问题,就是系统调用对于一个刚学操作系统的小白来说,让他使用是不是难度有点大了。站在系统开发的人的角度,可以直接调用各种系统调用,将系统接口封装成各种各样好用的函数,打包成库,对于开发者来说,很多功能就不用自己去写,直接使用库函数就可以了。(C/C++都会使用C标准库)。专业的人做专业的事,提高开发效率,降低开发成本。

用户使用起来会比较麻烦,所以就有了外壳程序(shell,图形化界面)。一个用户想要访问非常底层的OS数据或者硬盘,就一定要贯穿整个层次结构。

进程

一个程序运行起来是要被操作系统管理起来的,而操作系统可能不止一个这样的程序,操作系统都要将他们给管理起来,如何管理呢?

先描述,在组织。而描述的这一系列属性我们叫做进程的PCB。Linux操作系统下的PCB是: task_struct.

可执行程序加载到内存后,操作系统会为它创建PCB将它用数据结构管理起来,我们所的进程 = 可执行程序 + 内核数据结构(PCB)。而对进程的管理就转变成了对数据结构的增删查改。

一个进程的PCB不仅仅只在一个数据结构里!

为什么这样说,一个进程的PCB会在操作系统管理的进程的链表内,也有可能在各种的队列中,所以不要认为PCB只在一个数据结构中,它的PCB中可能存在一个链表指针也可能存在队列指针。

PCB中有什么呢?

标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息

在Linux中创建进程有两种方式:

  1. 命令行中直接启动进程,也就是我们执行的指令都是进程。
  2. 通过代码来进行进程的创建。

启动进程的本质就是创建进程,一般都是由父进程创建的。而我们命令行启动的进程,都是shell外壳程序,在Linux中也就是bash。所以,我们在命令行启动的程序都是bash的子进程。我们目前执行的指令,运行的软件可执行程序都是进程。

进程的标识符 pid,对应的每个进程还有父进程id是ppid
我们写的可执行程序就是进程都有对应的pid和ppid,我们是可以通过代码类获取的。

获取pid我们需要用到getpid这个函数。
Linux进程_第3张图片
获取ppid我们需要用到getppid这个函数。
Linux进程_第4张图片
我们写一段代码来验证一下!

#include 
#include 

int main()
{
    printf("my pid is %d\n",getpid());
    printf("my ppid is %d\n",getppid());
    
    return 0;
}

在这里插入图片描述

查看进程的第二种方式:

在Linux的根目录下有一个文件叫proc,他是专门存放所有存在的进程,这个进程目录的名称就是这个进程的pid。
我们可以写一个死循环来验证一下这个事情。

#include 
#include 
#include 

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

我们让这个代码运行的同时我们查一下它的pid。
在这里插入图片描述
我们可以看到这个文件是存在的,可以证明我们上面所说的,但是这个文件中有很多的内容。我们现在都不关心,我们只关心两个东西。
在这里插入图片描述
我们可以看到cwd是指向自己的当前的工作路径的,当前路径就是可执行程序执行是所处的路径,另外我们会发现还有一个exe是指向当前的可执行程序的。也就是说,一个进程都要有自己的工作目录,并且还要能找到自己的可执行程序。

当前的工作目录是可以修改的。用chdir()这个函数。

Linux进程_第5张图片

启动一个进程,怎么理解这种行为?

启动一个进程,本质就是系统多了一个进程,OS要管理的进程多了一个,所以启动一个进程的本质就是OS为该进程创建对应的PCB在Linux下就是task_struct,然后把它管理起来。

创建一个进程,又怎么理解这种行为?

我们的程序创建一个进程的本质,就是像内存申请空间,然后保存为该进程创建PCB,保存当前的可执行程序,然后把它放到OS的进程管理列表,让OS把它也管理起来,就是创建一个进程。我们会以父进程的PCB为模板为子进程创建,进程之间是具有独立性的,首先就体现在每个进程都拥有自己的PCB,但是我们现在创建的子进程是没有代码和数据的,所以就会和父进程共享代码和数据,代码是只读的,共享没问题,但是数据,父子进程是不一样的,换言之,数据父子进程是会修改的,所以父子进程必须保证数据自己各自私有一份,怎么做到的?写时拷贝!!

我们说我们可以用代码来创建进程,怎么创建呢?

可以使用fork函数来创建子进程。

Linux进程_第6张图片
如何区分子进程和父进程呢?我们看返回值!!
在这里插入图片描述
我们可以看到,对于父进程来说它的返回值是子进程的pid,而子进程的返回值是0。我们可以通过一段代码来验证这个事情。

#include 
#include 
#include 

int main()
{
    pid_t id = fork();
    //创建子进程
    if(id<0)
    {
        //创建失败
        return -1;
    }
    else if(id==0)
    {
        //为子进程
        int cnt = 5;
        while(cnt--)
        {
            printf("i am child, mypid is %d, myppid is %d\n",getpid(),getppid());
            sleep(1);
        }
    }else
    {
        // id一定大于0,为父进程
        int cnt = 5;
        while(cnt--)
        {
            printf("i am parent, mypid is %d, myppid is %d\n",getpid(),getppid());
            sleep(1);
        }
    }
    return 0;
}

我们看一下运行结果:
Linux进程_第7张图片
我们看到一份代码父进程和子进程在同时执行,此时我们的心里一定有一万个为什么,别着急,接下来给大家一 一解惑。

我们说fork是创建一个子进程,那么在创建之前一定是父进程在一个人执行代码,等执行fork之后进行了代码分流。但是fork之前的代码子进程也是可以看到的,因为代码是共享的,只不过子进程不会执行之前的代码,这是因为eip寄存器会指向fork之后的代码,分流之后,eip也会被子进程继承。

一个函数为什么会有两个返回值呢?
我们说创建一个进程一定会为它的子进程创建PCB,并用自己的PCB给它进行初始化,等创建完成以后,父子进程会共享代码,数据不共享,因为代码是只读的!那 return语句也是代码,在fork函数 return 是子进程已经创建完成,所以 return的时候就已经分流了。所以才会有两个不同的返回值。

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

有些东西一定是单进程无法完成的,比如一个程序想要边下载边播放音乐,单进程肯定不能完成。所以我们创建子进程是想让子进程帮助父进程完成一些工作。所以我们创建子进程就是为了让它做父进程不一样的事情,执行不一样的代码,所以才需要if的条件判断,然后父进程和子进程做不同的事情。

为什么fork给父进程返回子进程的pid,而给子进程返回0呢?
这时因为一个父进程可以有多个子进程,需要确保对应子进程的唯一性,而一个子进程只可能有一个父进程,所以都返回0。

一个CPU一次只能执行一个进程,那么父子进程fork之后,谁先执行?
这个和OS有关,谁先执行不确定,要看OS怎么决定。

那么今天的分享就到这里了,有什么不懂得可以私信博主,或者添加博主的微信,欢迎交流。

你可能感兴趣的:(Linux,linux,运维,服务器)