文章目录
- 基本概念
- “先描述”进程(PCB)
- task_ struct内容分类
- “再组织”进程
- 最终结论:
- 如何查看进程
- 方法一:
- 方法二:
- 通过系统调用获取进程标识符
- 通过系统调用创建进程子进程(fork)
- 实验一:
- 实验二:
- 实验三:
基本概念
在了解进程之前需要知道什么是程序?
对于计算机而言,程序就是系统可以识别的一组有序的指令。程序能指挥计算机执行我们想要它做的动作。程序储存在磁盘上,在执行时从磁盘到内存再到寄存器,最后被CPU执行。
进程的课本概念:程序的一个执行实例,正在执行的程序等,但这只是课本的概念。
“先描述”进程(PCB)
不可能在内存上只有一个进程,一般有很多程序在运行,那么操作系统怎么对这些进程进行管理呢?
一谈到操作系统的管理那么就一定是先描述。再组织,就像这样的先描述再组织的方式是操作系统一贯的作风。不了解先描述再组织的老铁可以看这篇文章文章链接
把程序加载到内存可不仅仅是把代码拷贝到内存,操作系统为了维护和管理这个进程也要创建对应的管理数据结构,就是这里的PCB(process control block),在Linux操作系统中描述进程的结构体叫做task_struct。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
本质上是一个装有进程属性的结构体。task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。一个程序对应一个task_struct结构体对象。
task_ struct内容分类
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
“再组织”进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里,
将程序对应一个task_struct结构体对象与它自己的代码和数据关联起来,根据程序对应PCB(task_struct)优先级和各种属性的不同,CPU对他们进行着不同的操作。
最终结论:
现在返回来看什么是进程,现在对于进程的概念就应该是:内核数据结构(task_struct)+进程对应的磁盘代码
如何查看进程
在window下我们可以直接通过任务管理器查看进程。
在Linux下如何查看进程呢?
方法一:
我这里先创建一个死循环程序
然后运行程序,在命令行输入
ps ajx |head -1 && ps ajx |grep 'myproc'
或者
ps ajx |head -1 && ps ajx |grep "myproc"
PPID是父进程ID,PID是这个进程自己的ID,PGID是这个进程的组ID,STAT代表这个进程的状态
UID是用户ID,COMMAND就代表我是哪一个进程
想杀掉这个进程用kill -9 这个进程的PID
方法二:
进程的信息可以通过 /proc 系统文件夹查看。
其实一个一个的进程也就相当于/proc下的一个个目录
通过系统调用获取进程标识符
- 如:要获取PID为1的进程信息,你需要利用
cd /proc/1
查看/proc/1这个文件夹。 那么怎么在程序里就直接获取PID呢?其实Linux提供了一个系统调用接口,叫做
getpid()
运行man getpid
认识一下getpid()
在我们的程序里添加这个系统调用,就会自动把当前程序的PID显示出来。
当我们利用kill -9
命令将这个11167的进程杀掉之后,那么在/proc下就看不到11167这个目录了。
然后我们再使用一下getppid来获取一下当前进程的父进程,
通过编译之后,多次运行,我们可以发现,PID也就是下面的id号是运行一次就变一次,而父进程pid号7139是一直都不变的,那么为什么呢?
我们在命令行输入ps ajx | head -1 && ps ajx | grep 7139
就可以看到7139是一个bash,我们把这个bash用kill命令杀掉的话,那我们的各种ls、cd等等一些个命令都运行不了了,我们退出shell下次再进行这样的操作,发现他们的父进程还是bash,所以其实在Linux命令行上启动的进程, 一般它的父进程没有特殊情况的话,都是bash!
通过系统调用创建进程子进程(fork)
- 运行
man fork
认识fork
- 下面是fork的返回值,惊奇的发现fork竟然有两个返回值
RETURN VALUE
On success, the PID of the child process is returned in the parent,
and 0 is returned in the child.
On failure, -1 is returned in the parent, no child process is created, and
errno is set appropriately.
- 父子进程代码共享,数据各自开辟空间,私有一份。fork执行之前只有一个父进程,函数执行之后是父进程+子进程
实验一:
下面用vim工具写一段代码:
运行起来可以看到printf被执行了两次,其中上面是父进程打印的,下面的是子进程打印的
通过ps ajx| head -1 && ps ajx | grep 29346
查看这个29346是谁,从下面结果就可以清晰的看到它是bash
从上面的结果来用一句形象的话来描述上面的现象就是:29945是爸爸,29346(bash)是爷爷,29946是孩子。
实验二:
代码:
执行结果:
RETURN VALUE
On success, the PID of the child process is returned in the parent,
and 0 is returned in the child.
On failure, -1 is returned in the parent, no child process is created, and
errno is set appropriately.
通过前面的介绍已经知道fork是有两个返回值的,可以看到其实它一个返回值返回给了父进程,这个返回值从上面可以看出它是子进程的id,另一个返回值是0,被返回给了子进程。
- fork 之后通常要用 if 进行分流
实验三:
代码:
执行结果:
可以看到在fork之后,会有父进程+子进程连个进程在执行后续代码。fork之后的代码,被父子进程共享,通过返回值的不同,让父子进程执行后续共享的代码的一部分。这就叫所谓的并发式编程。