目录
前言
开篇
一、进程概念
二、进程的描述与管理
1、如何描述与管理
2、Linux中的PCB-task_struct
3、对进程组织的理解
三、进程的属性
1、系统创建进程
2、查看进程
3、进程的标识符
4、退出进程
1>ctrl+c
2>kill命令杀死进程
5、用户进程的创建方式
前言
在学校里学习到进程,听老师讲的似懂非懂,每天都有在接触着进程,但是对于课本上的知识理解起来又是格外的难,这篇博客,我用Linux操作系统举例,来带大家深入的理解一下进程,摆脱对这个问题的困扰。
开篇
我们接触Windows系统的电脑较多,那在Windows系统的电脑里,我们也一直在跟进程打交道,举例说,当一个应用卡死的时候,我们一般会打开任务管理器,去结束该任务,此时罗列出来的一个个正在执行的任务,就叫进程。
我们可以通过我们的操作接口---结束任务去结束进程。
我们可以看到,操作系统中可以同时存在很多个进程,那操作系统是如何管理这么多进程的呢?
仍然是先描述,再组织!(前篇谈操作系统时提到过,一个很重要的概念)
那怎么谈“先描述,再组织”呢?我们接着往下看
一、进程概念
- 课本概念:程序的一个执行实例,正在执行的程序等
- 内核观点:担当分配系统资源(CPU时间,内存)的实体。
我们在课本上学习的时候,我们看到这个什么执行实例,正在执行的程序,也许都是一头雾水,这到底是什么意思?
注意,重点来了。
当我们所写的代码,我们编译时会生成一个二进制的可执行程序,这是进程吗?不是,这只是文件。
那当我们./去运行这个可执行程序,这时候操作系统需要进行哪些步骤呢?
根据冯诺依曼体系结构我们可知,我们运行可执行程序时它是需要先进到内存里的。那么这个可执行程序进入内存之前它在哪里呢?在磁盘里。
此时我们的可执行程序的代码和数据已经进入了内存,这里我有个疑问,可执行程序的代码和数据进入内存之后,它就是进程了吗?操作系统是怎么知道要执行它的呢?这个内存里面会有很多数据,操作系统又是怎么知道要去执行它的呢?又为什么要先执行它呢?
显然,只进入内存是不可以被称为进程的,他只是进程对应的代码和数据,因为它不足以满足我们上面提出的要求。那么操作系统要怎么去管理这些内存中的数据呢?让他们有序的执行呢?
要先描述,再管理!
二、进程的描述与管理
1、如何描述与管理
每一个进程都有一个PCB结构体,这个PCB结构体里面不仅包含了进程对应的属性,还有着一个,PCB类型的next指针,还存在着一个内存指针。这就是先描述。
其中,next指针作用是去指向下一个进程,内存指针去指向这个进程在内存中所在的位置,也就是指向了进程对应的代码和数据。组成了一个链表。这样,一个个进程都被连接起来,对这些进程的管理,就变成了对链表的增删查改。这就是再管理。
一个进程,一定要有一个PCB,此时我们就可以给进程一个概念
进程=PCB+自己的代码和数据。
2、Linux中的PCB-task_struct
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。课本上称之为PCB(process control block)
Linux操作系统下的PCB是: task_struct, task_struct是PCB的一种。 task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
那在我们Linux操作系统中,我们操作系统对进程的管理如图所示:
每一个进程都有一个tast_struct,当进程在排队等待的时候,本质上就是task_struct在排队等待罢了。
所以在Linux操作系统中,我们可以知道进程的概念就等于
进程=内核task_struct结构体+进程对应的代码和数据
3、对进程组织的理解
所有运行在系统里的进程都以task_struct链表的形式存在内核里。我们把它称为,内核数据结构。在内核源代码里可以找到它。
我们上述提到可执行程序会从磁盘到内存,我们电脑开机之前等待的那几十秒,就是在等待操作系统。
操作系统也是软件,它在没被运行之前也是一个二进制文件存在磁盘里,开机后,它会先到内存里,然后加载操作系统,再由操作系统内部去生成task_struct数据结构去管理进程。
三、进程的属性
1、系统创建进程
我们先在xshell里创建一个.c文件,写下如下代码
我们./运行它的可执行程序时,我们可以发现屏幕上会每隔一秒打印出这句话,它已经被运行了,此时他就是一个进程。
所以我们可以把我们的./运行换个说法,./可执行程序,本质上是让系统创建进程并运行
不单单有我们自己的可执行程序是进程,我们在使用每一个指令时,系统会在一瞬间自动对该指令创建进程并运行
那如何查看进程呢?
2、查看进程
指令:
ps axj
a就是all,xj表示系统所有的进程信息。
使用该指令,我们可以看到系统当下所有正在运行的进程。
但是我们查进程时一般只会查自己想查的进程,这时我们需要用管道过滤。
指令:
ps axj | grep <可执行程序>
拿我们刚刚创建的process可执行程序进行测试,在此之前,我们需要先运行我们的process可执行程序,运行后才是进程,才可以被查到。
此时有两个与process相关的进程被过滤出来,为什么是两个呢?正如我们上面所说,我们使用指令时,系统也会为其创建进程并运行,我们的管道对process可执行程序进行了过滤,所以也被过滤出来了,第一个才是我们process可执行程序./运行时系统为其创建的进程,第二个则是管道过滤时系统为管道指令创建的进程。
想要了解每列信息对应的含义,我们可以把该信息的头部罗列出来,我们可以使用指令:
ps axj | head -<查看行数>
我们只需要查看头部一行信息即可:
这就是进程的头部信息,我们再把process进程罗列在头部信息下面,方便我们去查看学习。
指令:
ps axj | grep head -1 && ps axj | grep process
3、进程的标识符
每一个进程对应的有自己的标识符,像我们的学号一样,是一种区分标志,操作系统来区分每一个进程就是根据每一个进程表示符的不同。
进程的标识符就是PID,在每一个进程的tast_struct中都有着每个进程独有的PID,方便操作系统来区分。
我们想要去访问进程的PID,是不可以直接去访问的,因为每个进程的task_struct属于操作系统内核数据结构,我们不可以直接去访问操作系统内部,所以我们就需要通过操作系统为我们提供的系统调用接口去访问进程PID。
这个系统调用接口就是getpid()
我们现在process.c文件中使用这个接口,再运行进程查看其PID
我们运行进程后去再去查看进程对应得PID,可以看到,process进程PID是2773
在左边PID的旁边,有一个PPID,这个是该进程对应的父进程的PID
我们也可以通过系统调用接口getppid()进行查看PPID.
4、退出进程
1>ctrl+c
当我们这个一直循环打印的进程无法结束时,我们可以ctrl+c使其强制结束
2>kill命令杀死进程
指令:
kill -9 <PID>
我们输入kill命令,我们可以发现,右边不断循环打印的进程被杀死了
5、用户进程的创建方式
我们想要创建一个进程,本质上是操作系统的内核数据结构中增加了一个task_struct,当然我们用户也不可以直接去访问操作系统,更不可能对操作系统的内核数据结构进行增删查改,所以,操作系统也为我们提供了一个系统调用接口fork()
我们写下如下程序,运行这个程序之前,我们思考,运行时会先打印出一行,创建子进程之后,我们的父进程和子进程又各自会运行下一行打印代码
可以理解为,fork()之后,父子代码共享
我们将查看进程信息窗口设置为每秒更新一次,这样每秒就会更新一下进程运行的状态
运行该进程