【Linux】12.Linux进程概念(1)
文章目录
- 1. 冯诺依曼体系结构
- 2. 操作系统(Operator System)
- 概念
- 设计OS的目的
- 胆小的操作系统
- 定位
- 如何理解 "管理"
- 总结
- 3. 进程
- 基本概念
- task_struct-PCB的一种
- task_ struct内容分类
- 组织进程
- 查看进程
- 通过系统调用获取进程标示符
- 通过系统调用创建进程-fork初识
1. 冯诺依曼体系结构
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
截至目前,我们所认识的计算机,都是有一个个的硬件组件组成
输入单元:包括键盘, 鼠标,扫描仪, 写板等
中央处理器(CPU):含有运算器和控制器等
输出单元:显示器,打印机等
关于冯诺依曼,必须强调几点:
这里的存储器指的是内存
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)
外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
一句话,所有设备都只能直接和内存打交道
存储器:内存
输入设备:鼠标,键盘,摄像头,话筒,磁盘,网卡…
输出设备:显示器,播放器硬件,磁盘,网卡…
有的设备是纯输入输出,也有的又是输入又是输出。
输入设备和输出设备统称为外设。
- 运算器:对我们的数据进行计算任务(算数运算,逻辑运算)
- 控制器:对我们的计算硬件流程进行一定的控制。
运算器和控制器统称为
CPU
。
上面这些东西都是独立的个体,所以要用总线连接起来。
总线分为系统总线和IO总线。
存储是有效率的。寄存器最快,缓存其次,内存其次,外存最慢。
2. 操作系统(Operator System)
概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库,shell程序等等)
设计OS的目的
与硬件交互,管理所有的软硬件资源(手段)
为用户程序(应用程序)提供一个良好的执行环境(目的)
胆小的操作系统
操作系统里面会有各种的数据,可是操作系统不信任任何用户。
操作系统为了保证自己数据安全,也为了保证给用户能够提供服务,操作系统以接口的方式给用户提供调用的入口,来获取操作系统内部的数据。
接口是操作系统提供的,用C实现的,自己内部的函数调用(系统调用)
所有访问操作系统的行为,都只能通过系统调用实现。
定位
在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件
如何理解 “管理”
管理的例子
描述被管理对象
组织被管理对象
操作系统可以认为是决策者,驱动程序可以认为是执行者,软硬件资源可以认为是被管理者。
先描述,再组织。
在操作系统中,管理任何对象,最终都可以转化为对某种数据结构的增删查改。
C/C++
的库函数和系统调用之间的关系就是上下层的调用和被调用的关系。
总结
计算机管理硬件
- 描述起来,用
struct
结构体 - 组织起来,用链表或其他高效的数据结构
3. 进程
基本概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体。
描述进程-
PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
课本上称之为
PCB
(process control block),Linux操作系统下的PCB
是:task_struct
一个操作系统,不仅仅只能运行一个进程,还可以同时运行多个进程。
操作系统必须严格讲进程管理起来。
任何一个进程在加载到内存的时候,形成真正的进程时,操作系统要先创建描述进程
属性
的结构体对象PCB
– 进程控制块。可以认为
PCB
就是进程属性的集合,strut
结构体里面存放的是进程编号,进程的状态,优先级…进程 = 内核PCB的数据结构对象(描述你这个进程的所有的属性值) + 你自己的代码和数据
task_struct-PCB的一种
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
task_ struct内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct
链表的形式存在内核里。
在操作系统中,对进程进行管理,可以抽象为对一个个PCB
连接起来的一个单链表的增删改查。
pcb -> task_struct
结构体,里面包含进程的所有属性。
Linux中树如何组织进程,Linux
内核中,最基本的组织进程task_struct
的方式,采用双向链表组织的。
查看进程
进程的信息可以通过ps
或者 /proc
系统文件夹查看。
简单写一个简单程序
我这里把这个进程杀掉,那个进程就自动死掉了。(这里的进程号和上面不同是因为我中间关掉了程序一次)
通过系统调用获取进程标示符
进程id(PID)
父进程id(PPID)
proc.c
#include <stdio.h>
#include <unistd.h>
int main(){
while(1)
{
printf("I am a process,my id is:%d\n",getpid());
sleep(1);
}
return 0;
}
输入:
while : ; do ps ajx | head -1 ; ps ajx |grep proc |grep -v grep;echo"----------------------";sleep 1;done
# while : ; do ... done
# 创建一个无限循环
# : 是一个始终返回true的命令
# 会一直循环执行直到你按 Ctrl+C 停止
# ps ajx | head -1 # 显示进程列表的表头
# ps ajx | grep proc | grep -v grep # 查找名为"proc"的进程(排除grep进程本身)
# echo "----------------------" # 打印分隔行
# sleep 1 # 暂停1秒
一开始没有运行程序,后来开启了进程。
然后改一下proc.c
的代码
#include <stdio.h>
#include <unistd.h>
int main(){
while(1)
{
printf("I am a process,my id is:%d,parent:%d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
里面的父进程和子进程的id也能对的上。
这里每次子进程的pid都在变,但是父进程的ppid却不变。
这个父进程是谁呢?
原来是bash
。
这个bash
在我们每次登录系统的时候,系统会默认分配一个bash
进程给我们。
通过系统调用创建进程-fork初识
运行
man fork
认识fork
fork
有两个返回值父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("我是一个进程,pid:%d, ppid:%d\n",getpid(),getppid());
pid_t id=fork();
if(id==0){
//子进程
while(1){
printf("我是子进程,pid:%d, ppid:%d\n",getpid(),getppid());
sleep(1);
}
}
else if(id>0){
//父进程
while(1){
printf("我是父进程,pid:%d, ppid:%d\n",getpid(),getppid());
sleep(1);
}
}
else{
//error
}
return 0;
}
这里面出现了我是子进程
和我是父进程
,说明fork
返回了两个值,简直是匪夷所思啊,以前的函数明明只能返回一个值的。
这里说明,fork
又创建了一个进程,并且出现了父进程和子进程的死循环。
一开始这个程序是只有一个进程的,后来多出来一个子进程,形成了一个分支。
为什么
fork
要返回两个值?给父进程返回子进程的pid
,给子进程返回0
呢?返回不同的返回值,是为了区分,让不同的执行流,执行不同的代码块。
一个函数是如何做到返回两次的?如何理解?
fork
也是个函数,当一个函数到了return
语句的时候,他应该执行的核心工作已经实现完了。
return
语句也是代码,是被父子进程所共享的,所以父进程的return
执行好了 会执行子进程的return
。
fork
函数究竟在干什么?干了什么?
进程=内核数据结构+代码和数据
一开始只有一个进程,也就是父进程,他有它对应的数据和代码。
然后新创建了一个子进程,子进程刚被创建出来的时候,是没有对应的代码和数据的,所以会指向父进程对应的代码。
所以fork
之后,父子进程的代码是共享的。
但我们创建子进程肯定是想让子进程完成和父进程不一样的事情,所以我们要想办法让父和子进程执行不同的代码块。
这就让fork
有了不同的返回值。
- 一个变量为什么会有不同的内容?如何理解?
在任何平台,进程在运行的时候,是具有独立性的。我一个进程崩了不会影响其他的进程。
上面讲到,子进程会和父进程共用代码块,但是子进程没有数据,只能也死皮赖脸的从父进程拷贝一个数据,这个操作是操作系统自动完成的。
于是,两个进程的数据独立,代码公共,自然可以返回两个不同的内容了。
当然上面只是我们期望的,实际上子进程直接拷贝父进程的数据会导致数据太冗余了。
所以操作系统在子进程要对父进程的数据进行修改的时候,要改哪段,就会申请哪段的数据,可以称为数据层面的写时拷贝。
当子进程不修改父进程数据时,会采用写时父进程的数据和代码。
父子进程创建好后,
fork
先运行父进程还是子进程?不确定,谁先运行由调度器确定。
调度器:选择运行哪个进程。(尽可能地平均和公平)