
各位读者大佬好,我是落羽!一个坚持不断学习进步的学生。 如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步! 也欢迎关注我的blog主页: 落羽的落羽
大多数的计算机,如笔记本,手机,服务器,大多遵循着冯·诺依曼体系结构:

众所周知,计算机其实都是由一个个的硬件组成的:
从这个体系结构图中我们能看出:在数据层面上,CPU不会和外设(输入输出设备)进行交互,CPU只会和内存打交道。外设要输入或输出数据,只能写入内存中或从内从中读取。数据可以提前加载到内存中,所以计算机处理数据可以认为主要就是CPU和内存之间的交互。 总结就是,所有设备只能直接和内存打交道,所以计算机的效率主要取决于内存。而它们之间的数据流动过程,本质都是拷贝,也就是计算机的效率由拷贝效率决定!
任何一个计算机系统都包含一个基本的程序集合,称为操作系统(OS)。广义上理解,它包含操作系统内核、其他程序(函数库、shell程序等)
操作系统的内核是它的核心,内核有四大功能:内存管理、文件管理、驱动管理、进程管理。
在整个计算机软硬件架构中,操作系统是一款软件,专门用于软硬件资源管理!
想象这样一个场景:用户到银行取钱,用户是上层软件,金库是计算机硬件。如果让上层软件直接操控底层硬件,就像让用户直接去金库拿钱,管理起来就十分麻烦!所以就需要有操作系统进行中间管理——操作系统就像银行柜员,用户与柜员沟通存取钱,柜员再负责向金库中存取钱。这样一来从整个银行行长角度看,管理软硬件资源就很方便了。
在程序员开发角度,操作系统对外会表现为一个整体,暴露部分接口给上层开发使用,这部分操作系统提供的接口,称为系统调用。
进程是操作系统学科中一个很重要的知识,操作系统的进程管理、内存管理、文件管理、设备管理等核心功能,都围绕进程展开。
一般的教科书上可能讲进程的概念是:程序的一个执行实例、正在执行的程序、是程序的一次执行过程,等。或者从内核层面来看,进程是担当分配系统资源(CPU时间、内存等)的实体,是操作系统进行资源分配和调度的基本单位等,没有错。
我认为更精辟的总结是:进程 = 内核数据结构对象 + 程序的代码和数据
这个概念需要我们在后面的学习中慢慢体会。
Linux系统的内核主要是由C语言写的。
一个进程的信息,被存放在一个叫做进程控制块的数据结构中,是进程属性的集合。在操作系统学科中,这个数据结构统称为PCB(process control block)。具体在Linux系统中,它是一个C语言结构体,名字是task_struct!
task_struct中有很多的信息,在接下来我们学习进程会不断了解的。
在Windows系统下双击启动应用、手机上启动app、Linux中执行命令和程序,本质都是启动了进程!我们也有相应的方法去查看进程。
命令ps axj,查看系统中所有进程的部分信息:

当前系统中还有很多隐藏的进程,为了方便查看到我们想要的进程,可以搭配head和grep命令使用。
为了方便演示,我写一个这样的程序test.exe:

这个程序可以一直运行,除非ctrl c中断。
我把它运行起来,然后新开一个终端页面,执行:
ps axj | head -1 && ps axj | grep test.exe (head -1是为了保留第一行列名,grep是为了查询有test.exe文本的进程)
结果是:

可以看到,查询结果有两行,第二行是上面ps命令的进程(因为说过执行命令也是启动了进程)不用在意。重点是第一行,这就是我们正在一直运行的test.exe程序的进程信息!
有一列名是PID,是进程id,用来描述本进程的唯一标识符,用来区别其他进程。 一个进程可以获取到自己的pid,OS提供了一个系统调用getpid,返回当前进程的pid

使用它需要包含头文件#include<sys/types.h>和#include<unistd.h>,返回类型pid_t其实就是一个整型。
可以证明一下,在test.exe中加一句printf("%d\n", getpid());

还有一列为PPID,是父进程id!
Linux中,新的进程往往是通过其他进程创建的,这就是父进程。ppid就是一个进程的父进程id。
刚才说过,执行命令也是创建了进程,那么这个进程的父进程是谁呢?在上图中第二行是执行的命令,它的ppid是8059,那么我们就可以查看一下谁的pid是8059,ps axj | head -1 && ps axj | grep 8059:

😮进程id是8059的是一个叫-bash的东西! -bash,是Linux系统的命令行解释器,是所有命令行执行命令的进程的父进程!也就是说,我们执行Linux的命令,本质都是-bash创建了子进程!
也有一个叫做getppid()的系统调用,可以获取到当前进程的ppid,用法和getpid一样,不再赘述。
刚才说,一个进程是由父进程创建的。我们自己写的程序启动后也是一个进程,按理说也能创建子进程。Linux就有这样一个系统调用fork!

fork函数的返回值很有意思:

调用fork后,会创建一个子进程。在父进程中,fork的返回值是子进程的pid;在子进程中,fork的返回值是0!如果子进程创建失败则在父进程中返回-1 默认条件下,fork后父子进程是共享代码和数据的!
举个栗子:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
// 创建子进程
pid_t id = fork();
while(1)
{
if(id == -1)
{
//子进程创建失败,一般不会失败
return 1;
}
else if(id == 0)
{
//子进程中执行
printf("子进程,pid:%d\n", getpid());
}
else
{
//父进程中执行
printf("父进程,pid:%d\n", getpid());
}
sleep(1);
}
return 0;
}执行后:

为什么fork的返回值是这样规定的呢?这是因为一个子进程只会有一个父进程,而一个父进程可以创建出多个子进程,为了标识特定的子进程父进程需要得到子进程的pid。