当我们的代码进行编译链接后便会生成一个可执行程序,这个可执行程序本质上是一个文件,是放在磁盘上的。当我们双击这个可执行程序将其运行起来时,本质上是将这个程序加载到内存当中了,因为只有加载到内存后,CPU才能对其进行逐行的语句执行,而一旦将这个程序加载到内存后,我们就不应该将这个程序再叫做程序了,严格意义上将应该将其称之为进程.







ps aux | head -1 && ps aux | grep bash | grep -v grep

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
printf("I am a process And My Pid == %d,PPid == %d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
我们可以通过ps命令查看该进程的信息,即可发现通过ps命令得到的进程的PID和PPID与使用系统调用函数getpid和getppid所获取的值相同.

我们会发现,当我们每次启动进程的时候,所对应的进程的pid不一样是正常,但是所对应的父进程都是一样的,都是命令行解释器.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
fork();
while (1)
{
printf("I am a process And My Pid == %d,PPid == %d\n",getpid(),getppid());
sleep(1);
}
return 0;
}

有的uu会有些疑问,加载到内存当中的代码和数据是属于父进程的,那么子进程的代码和数据又是从何而来的呢?我们来看下面这段代码
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("one process is running,pid == %d,ppid == %d\n",getpid(),getppid());
sleep(3);
fork();
printf("hello vim,pid = %d,ppid = %d\n",getpid(),getppid());
sleep(5)
return 0;
}
使用fork函数创建子进程,在fork函数被调用之前的代码被父进程执行,但是在fork之后父进程与子进程代码共享,创建一个进程本质就是系统多一个进程,多(1):task_struct(2)代码 + 数据.

ork函数创建出来的子进程与其父进程共同使用一份代码,但我们如果真的让父子进程做相同的事情,那么创建子进程就没有什么意义了。实际上,在fork之后我们通常使用if语句进行分流,即让父进程和子进程做不同的事。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("one process is running,pid == %d\n",getpid());
sleep(3);
pid_t id = fork();
//fork函数的返回值等于0则为子进程.
if(0 == id)
{
while (1)
{
printf("I am child process and my pid == %d,ppid =%d\n",getpid(),getppid());
sleep(1);
}
}
//返回值为非0则为父进程.
else
{
while (1)
{
printf("I am parent process and my pid == %d,ppid =%d\n",getpid(),getppid());
sleep(1);
}
}
return 0;
}fork创建出子进程后,子进程会进入到 if 语句的循环打印当中,而父进程会进入到 else 语句的循环打印当中

虽然fork之后,后面的代码是被父子进程共享的,但是进程一定要做到:进程具有独立性. 进程 = 内核数据结构task_struct + 代码 + 数据(数据层面,父子进程各自独立,原则上数据要分开).

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
void Runchild()
{
while(1)
{
printf("I am child Process and My pid = %d,ppid = %d\n",getpid(),getppid());
sleep(1);
}
}
int main()
{
const int num = 5;
for(size_t i = 0; i < num; i++)
{
size_t id = fork();
//fork的返回值若为0则创建的是子进程,若返回值大于0则是父进程
if(0 == id)
{
Runchild();
}
sleep(1);
}
//当循环结束了,则说明创建的子进程结束了
while(1)
{
sleep(1);
printf("I am parent process:pid = %d,ppid = %d\n",getpid(),getppid());
}
return 0;
}

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
printf("I am process and my pid == %d\n",getpid());
sleep(1);
}
return 0;
}

我们可以看到,当将进程所对应的可执行程序删除了,为什么该进程还能够运行呢,因为在原则上,一个程序要能够被调度,要在内存中里面存有一份可执行程序,磁盘上的可执行程序被删除了,但是内存空间中还有.


在C语言阶段的时候,我们学习过文件,并且使用过fopen来创建文件,但是有的uu会比较好奇,为什么每次使用代码创建文件的时候,总是在当前路径下创建呢?我们来看两个例子.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
FILE * pf = fopen("test.txt","w");
while (1)
{
printf("I am process and my pid == %d\n",getpid());
sleep(1);
}
return 0;
}
我们可以看到,当运行程序时,在当前路径下创建了test.txt的文件,这是为什么呢?原因在于

在举例之前,我们得先学习一下chdir函数,此函数的功能是:更改进程的工作路径.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
chdir("/home/ly/111");
FILE * pf = fopen("test.txt","w");
fclose(pf);
pf = NULL;
while (1)
{
printf("I am process and my pid == %d\n",getpid());
sleep(1);
}
return 0;
}

一个进程从创建而产生至撤销而消亡的整个生命期间,有时占有处理器执行,有时虽可运行但分不到处理器,有时虽有空闲处理器但因等待某个时间的发生而无法执行,这一切都说明进程和程序不相同,进程是活动的且有状态变化的,于是便有了进程状态这一概念.

这里博主要带uu们谈一谈Linux操作系统中的进程状态,Linux操作系统的源代码当中对于进程状态有如下定义:
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};进程的当前状态是保存到自己的PCB中的,在Linux操作系统中也就是保存在task_struct中的.

S状态的本质是:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
printf("I am process and my pid == %d\n",getpid());
sleep(1);
}
return 0;
}
通过观察我们可以发现,在注释前后,进程的状态由最初的S+状态变成了R+状态,那么这是为什么呢?
那么有的uu就会问,能否保证每次打印的时候,显示器都是处于就绪状态呢,答案:并不是. 因为程序在内存中是通过CPU跑起来的,CPU的运算速度相较于显示器这个外设来说,CPU的运算速度要比显示器快的多,进程在被调度时,要访问显示器资源,因为printf函数的本质是往显示器上打印,所以大部分时间,进程都在等待显示器资源是否就绪,那么所以查到的进程状态就是R状态,但实际上在向显示器资源打印的时候,进程一直在等待显示器资源就绪,如果显示器资源不就绪,那么进程就会处于S状态,只有在真正执行printf函数的时候,才能看到进程处于R状态,但是对于CPU来说,执行printf函数是十分的快速的,大概是几纳秒左右,但是打印的时候可能是几毫秒完成.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
sleep(20);
printf("I am process and my pid == %d\n",getpid());
}
return 0;
}
在睡眠的20s期间,中间突然ctrl + c杀掉进程,那么此时睡眠状态就被中断了,因此S+状态是可中断的睡眠状态.

在Linux中,我们通过kill指令来向进程发送信号

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
sleep(1);
printf("I am process and my pid == %d\n",getpid());
}
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
sleep(1);
printf("I am process and my pid == %d\n",getpid());
}
return 0;
}#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
while (1)
{
sleep(1);
printf("I am process and my pid == %d\n",getpid());
}
return 0;
}

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("I am child process,cnt:%d,pid:%d\n",cnt,getpid());
sleep(1);
cnt--;
}
}
else
{
while(1)
{
printf("I am parent process,pid:%d\n",getpid());
sleep(1);
}
}
return 0;
}


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("I am child process,cnt:%d,pid:%d\n",cnt,getpid());
sleep(2);
cnt--;
}
}
else
{
int cnt = 5;
while(cnt--)
{
printf("I am parent process,cnt:%d pid:%d\n",cnt,getpid());
sleep(1);
}
}
return 0;
}




那么有的uu会比较好奇,一个进程一旦有CPU,那么CPU会一直运行到这个进程结束吗?









竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便有了优先级。
独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行: 多个进程在多个CPU下分别同时进行运行,这称之为并行。
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发.