
个人主页:小则又沐风
个人专栏:<数据结构>
<竞赛专栏>
<Linux>
座右铭
路虽远,行则将至;事虽难,做则必成
目录
[ps ajx](#ps ajx)
前言
在我们进入今天的学习的之前,我们先来总结一下我们之前学习的东西是什么?
在之前的文章中我们学习了Linux的简单的指令并且我们了解了权限并且我们试着学习了在Linux下的开发工具.
不知道大家发现了吗----我们之前学习的东西好似练功需要的内功,我们并没有深度的了解深层的东西.俗话说的好:工欲善其事必先利其器
现在我们就迈进进程这个大关.
今天我们就来了解一下进程的概念.
理解操作系统
冯诺依曼体系
在我们了解操作系统之前我们先了解一下这个冯诺依曼体系

输入设备:就是我们的键盘.麦克风.....
存储器:就是我们常说的内存
输出设备:显示器,打印机....
但是在这个图中最重要的是这个中央处理器
也就是我们常常说的cpu
我们需要知道的是cpu=运算器+控制器
从这个图中我们可以看到能和cpu进行数据交换的只有内存.输入输出设备都无法把数据直接的交给cpu
这就导致了我们想要处理一个数据的话
首先输入设备输入这段数据->传给内存->cpu从内存中得到这段数据->处理的结果交给内存->输出设备得到结果
所以就可以得知cpu是一个非常重要的了-----我们也可以知道一个计算机的运行的速率就取决于cpu从内存中获得数据的快慢了
我们来看这一张图片

这是存储器的图表
距离cpu越近的话传递数据的速度就越快,相应的价格就越贵
操作系统的概念
任何计算机系统都包含⼀个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理,内存管理,⽂件管理,驱动管理)
其他程序(例如函数库,shell程序等等)

总的来说操作系统就是一个进行对软硬件管理的软件

现在我们来看一个场景:
家中的一个老人,我们给他了一个手机,他想听相声,但是他对于这个手机是一窍不通,根本不知道怎么使用这个东西.
现在回归我们的操作系统:
我们何尝不是这个一窍不通的小白呢?
所以为了用户更好的使用操作系统,所以我们不会直接的对系统,进程什么的直接进行管理
所以我们这个操作系统把那些底层东西都进行的封装,我们上层的用户只需要一个函数的调用
当然这个函数可能会进行系统的调用.这样我们就可以开开心心的访问底层了
所以操作系统是怎么进行管理的?
在理解操作系统的管理的方法之前我们现在来看一个例子:
我们是某某大学的学生.
现在要举行篮球比赛
校长要选10个成员去比赛,那么校长在这里就是操作系统,他要对学生的数据进行管理
那么怎么管理学生的数据呢?
我们知道我们入学会有自己的档案,我们的所有的信息都会存储在里面
我们的校长就可以搞一个表格
学生姓名 年纪 性别 身高 体重 ....
这样把每个学生的数据填入进去一个个选就可以了
这是一种的管理方法
现在我们的校长看出来这个管理的方法太慢了
他找到了我们学校的教C语言的老师让他给他想个办法
C语言老师说不是问题,随后他就设计了一个结构体
这个结构体就是存储这学生的各个信息的
struct student
{
int age;
string name;
int wieght;
......
}
然后把每一个的学生的数据都存到了这个结构体中了,然后现在出现了一个问题,这些结构体是一个个离散的,怎么串联到一起??
这难不倒C语言老师,因为他熟练数据结构
他就在结构体中加入了这个
student * next;
这个就把我们的结构体像一个个链表一样串联了起来,
现在想要得到身高的前10名就按照身高排序结构体...
这样我们的学生的数据就完成了管理
这个例子有什么启示:
实际上我们在生活中想要对数据进行管理的话,我们的方法就是:
先描述,在管理
在上面我们对一个个学生的管理就变成了对一个个结构体的管理了,想要开除一个学生,直接把他的结构体删除了...
当然操作系统的管理也是上述的方法
现在我们了解了操作系统的管理方法是这样的
那么我们的进程是怎么描述的呢???
进程的概念
• 课本概念:程序的⼀个执⾏实例,正在执⾏的程序等
但是这个概念太过于抽象了我们怎么理解这个进程呢???
我们知道的其实我们的每一个的程序都是一个个的可执行的程序,每一个可执行的程序都是一个进程.
描述进程
那么我们的cpu是怎么运行的一个个的程序的呢?
首先我们需要知道的是cpu肯定是由一个进程的列表的.把一个个需要运行的进程挂入到这个链表中,但是我们的进程是以什么样子的状态来连接到这个链表上的呢?
我们知道的是我们写的C++的可执行的程序,也是一个进程,我们写的可执行程序都有什么??
包含我们的数据和代码
所以我们的进程也需要包含这些东西
现在直接来公布一下进程是怎么描述的
在底层的代码的实现中有一个用来描述进程的结构体
他的名字就是task_struct
这个结构体包含了我们进程的信息(其中包含代码,数据)
但是在这个结构体中我们的数据并不是把他拷贝一份到我们的结构体中而是把我们的数据和代码存储的地址放在了结构体中了,可以按照这个地址找到我们的代码和数据
所以我们的进程就是task_struct+代码和数据
task_struct
我们在上面的认识中知道了这个结构体就是用来描述进程的
但是这个结构体中包含有有关进程的什么内容呢?
标⽰符:描述本进程的唯⼀标⽰符,⽤来区别其他进程。
• 状态:任务状态,退出代码,退出信号等。
• 优先级:相对于其他进程的优先级。
• 程序计数器:程序中即将被执⾏的下⼀条指令的地址。
• 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
• 上下⽂数据:进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]。
• I∕O状态信息:包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表。
• 记账信息:可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
• 其他信息
• 具体详细信息后续会介绍
查看进程
ps ajx
在查看进程的之前我们先创建出一个进程
cpp
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
printf("wo shi yi ge jin cheng\n");
sleep(1);
}
return 0;
}
创建出一个C语言的文件
然后我们编译运行它
然后我们直接用ps ajx查看进程的相关的信息

/proc
但是上面的ps的使用是给我们把进程的信息是给我们呢包装好的,但是如果我们想要查看更多的信息的话我们就需要这个命令行了
这个命令行的使用是这样的

现在我们同时登录两个这个用户,一个运行起来我们的进程,然后我们在第二个的机器上来查看进程的信息
我们在查看进程的信息的时候我们需要知道的是这个进程的pid(之后会讲解)

在这个上面我们得到了这个进程的pid下面我们来查看一下这个进程的相关的信息
/proc/pid
这个代表的就是这个pid进程的信息的目录

在这个目录里面我们需要了解的有两个东西

cwd这个记录的是这个可执行的文件所处的路径,所以我们就可以理解下面的场景了
cpp
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
FILE * f=fopen("text.txt","w");
if(f==NULL)
{
exit(1);
}
//while(1)
//{
//printf("wo shi yi ge jin cheng\n");
//sleep(1);
//}
return 0;
}
我们来看看这个text的文件是被创建到了什么地方

可以看到这个text的文件是在创建在了这个可执行文件的相同的路径中了
但是为什么会这样呢?
这就是因为这个文件就是我们进程创建的文件,如果我们不自己显示的说明把这个文件创建在哪里的话,他就会根据进程的cwd创建在默认的这个路径下了
那么是不是修改了这个cwd就会改变这个默认的创建的路径呢?
我们来验证一下
int chdir(const char *path);
这个函数是可以修改进程的cwd的
cpp
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int op =chdir("/home/jiao/study/5.11");
if(op==-1)
exit(1);
FILE * f=fopen("text.txt","w");
if(f==NULL)
{
exit(1);
}
//while(1)
//{
//printf("wo shi yi ge jin cheng\n");
//sleep(1);
//}
return 0;
}
现在我们来运行一下看看结果


果然不出所料这个text文件出现在了5.11这个目录下了

这个呢就是这个进程所对应的可执行的文件了
pid和ppid
我们先来了解一下这个pid和ppid的内涵
在上面我们就已经使用pid来查找一个进程的信息了
所以可以看到这个pid像一个身份证一样,一个pid对应一个进程
我们需要知道的是每一个进程都是由父进程创建的而这个ppid就是父进程的pid
通过系统调用获得pid/ppid
在上面我们是可以通过ps获得pid但是在我们的可执行的文件中我们怎么得到这个进程的pid呢?
我们可以通过这个函数
getpid();
getppid();
cpp
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
sleep(1);
printf("我自己的pid是->%d\n",getpid());
printf("我的父进程pid是->%d\n",getppid());
}
//int op =chdir("/home/jiao/study/5.11");
//if(op==-1)
//exit(1);
//FILE * f=fopen("text.txt","w");
//if(f==NULL)
//{
//exit(1);
//}
//while(1)
//{
//printf("wo shi yi ge jin cheng\n");
//sleep(1);
//}
return 0;
}

通过系统调用创建进程
我们可以通过
pid_t fork(void);
这个函数来创建出一个子进程
首先需要知道的是这个函数的返回值如果这个返回值是一个<0的值的话那么就是我们创建子进程失败了
如果返回值是0的话那么代表的就是现在这个进程是子进程
如果不是上面的两种话就是父进程
现在我们了解一下创建出子进程的特征:
cpp
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
while(1)
{
sleep(1);
printf("我要开始创建进程了,我是父进程\n");
fork();
printf("程序开始运行了\n");
}
//while(1)
//{
//sleep(1);
//printf("我自己的pid是->%d\n",getpid());
//printf("我的父进程pid是->%d\n",getppid());
//}
//int op =chdir("/home/jiao/study/5.11");
//if(op==-1)
//exit(1);
//FILE * f=fopen("text.txt","w");
//if(f==NULL)
//{
//exit(1);
//}
//while(1)
//{
//printf("wo shi yi ge jin cheng\n");
//sleep(1);
//}
return 0;
}

这里就可以知道了我们子夫进程的代码是共享的,更确切地说是子进程使用地也是父进程的代码
那么我们来看下面的代码
cpp
#include <stdio.h>
#include<stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
while(1)
{
sleep(1);
if(ret<0)
{
return 1;
}else if(ret==0)
{
printf("我是子进程,我的pid是->%d,我的父进程的pid是->%d\n",getpid(),getppid());
}else
{
printf("我是父进程.我的pid是->%d,我的父进程的pid是->%d\n",getpid(),getppid());
}
}
//while(1)
//{
//sleep(1);
//printf("我要开始创建进程了,我是父进程\n");
//fork();
//printf("程序开始运行了\n");
//}
//while(1)
//{
//sleep(1);
//printf("我自己的pid是->%d\n",getpid());
//printf("我的父进程pid是->%d\n",getppid());
//}
//int op =chdir("/home/jiao/study/5.11");
//if(op==-1)
//exit(1);
//FILE * f=fopen("text.txt","w");
//if(f==NULL)
//{
//exit(1);
//}
//while(1)
//{
//printf("wo shi yi ge jin cheng\n");
//sleep(1);
//}
return 0;
}

这个就有点违背我们的认知了
为什么这个fork的返回值有两个???
现在来简单的解释一下:
我们的fork的函数的内部肯定一定经过创建出了子进程,所以我们的子进程运行他的,父进程运行他的所以返回值就有了两个.
但是为什么这个ret可以同时满足if和else在我们学过虚拟地址空间我会详细的讲解
现在简单的说就是进行了写时拷贝了
总结
这篇文章先从冯诺依曼体系和操作系统的基础讲起,把地基打牢;再讲进程到底是什么,内核又是怎么用 task_struct 来描述它的;接着教大家用 ps ajx、/proc 文件系统看进程,用 getpid()、getppid() 拿 PID 和 PPID;最后落到进程创建上,把整个体系串起来,希望能帮大家把这块儿彻底吃透。
谢谢大家的观看!!!
之后会继续讲解进程的知识.