🌈个人主页:羽晨同学
💫个人格言:“成为自己未来的主人~”
我们上一篇文章当中讲到了进程的概念,我们知道进程信息是放到一个tast_struct的结构体当中的,这个结构体也叫做PCB。
PCB是内存级的操作,我们把这个结构体放到CPU当中。
首先,我们来说一下应该如何查看进程呢?
比如说,我们创建一个code的进程。
#include<stdio.h>
int main()
{while(1){printf("hello world\n");}return 0;
}
~
~
然后我们将这个代码运行起来,就创建了一个进程,接下来,让我们来查看我们的进程。
ps ajx | head -1 && ps ajx | grep code
通过这个命令,我们就可以查看到下面的进程列表。
其实这个命令本身就是好几个进程。
ps是一个,head是一个,grep本身就是一个,然后查看到的code也是一个,当我们想要取消掉grep进程的时候,可以后面加一个grep -v grep,就可以过滤掉grep进程。
这个时候,就没有grep进程了。
其实把程序运行起来,本身就是在系统上启动一个进程。
像linux当中的指令,如:ls who am i pwd等都是系统对应的指令,都是系统对应的文件,相对应的,这种也都是启动的进程,不过这种进程是瞬时的,一跑就跑完了。
还有一种不是瞬时的,就比如各种应用程序。
所以,进程分为两种:
执行完就推出的。
除非用户主动关闭,否则一直不退的(常驻进程:杀毒软件)
为了识别进程,所以进程是有编号的,也就是进程的pid。
同一个程序,在不同的时间启动,pid是会发生变化的,背后的原理是用了一个累加的计数器。
如果连续创建,pid是会连续的。
如果是我们刚才创建的进程,这个时候,他的PID是17293,当我们重新启动这个进程。
这个时候他的PID是19421
此外,进程是具有唯一性的,体现到PID上就是,每个进程的PID都是不同的。
但是进程又是可以知道自己的PID的
pid_t pid getpid();
通过这条指令,我们就可以获得进程的id值
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{pid_t id = getpid();while(1){printf("hello bit,i am a process pid: %d\n",id);sleep(1);}return 0;
}
~
你看,这样子的话,我们就可以获得进程的PID值了
我们取消进程的方式一般有两种,一种是ctrl c 还有一种是使用kill命令,其实两个方式的本质相同,都是杀进程。
比如说,这个时候我们启动一个进程,我们可以使用下面的这个命令来删除进程:
kill -9 21858
这个时候,这个进程就被删除了
在Linux中,在根目录下面有一个/proc(目录),全称叫做process,把进程的很多信息以文件的形式呈现出来。
这里面的一个个数字都是代表着进程的PID,每一个目录都是代表的一个进程。
而每一个目录里面都存放着这个进程的所有属性。
只要启动了一个进程,就会瞬间在/proc里面创建一个这个PID的文件夹。
比如说,我刚才创建一个22877的进程,就可以在里面直接打开这个进程。
再次把这个进程干掉之后,就找不到这个文件夹了,也就没有22877这个东西了。
当我们进入这个目录的时候,有两个东西是值得我们关注的。
一个是exe,一个是cwd,
首先,我们说一下exe。
当我们这个时候终止这个进程,那就是删除了这个可执行文件。
但是这个时候代码还在运行,这是因为这个时候已经加载到内存当中了,删除的是磁盘当中的可执行文件。
而对于那个cwd来说,cwd指的是current work dir,是当前的工作目录。
我们创建程序或者文件的时候,一般都是默认在当前目录下面创建的。,而这个当前目录,就是cwd所指向的路径。
比如说,这个时候我们新建一个程序:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{FILE *fp=fopen("log.txt","w");if(fp==NULL){}pid_t id = getpid();while(1){printf("hello bit,i am a process pid: %d\n",id);sleep(1);}return 0;
}
当这个代码运行之后,我们发现在当前目录下面创建一个log.txt的文件夹
怎么说呢,其实就是自己写的代码,当开始跑的时候,从上到下开始执行的时候,就是将cwd表示的路径拼接到log.txt的前面。所以会在当前路径下面新建文件。
如果想要改变工作路径的话,我们可以使用 int chdir,参数就是一个新的路径
比如说
chdir("/");
这个时候我们将默认的工作路径改到了根目录下面。
这个时候我们启动进程,看一下当前的cwd的指向。
看,这个时候的cwd就指向了根目录。
但是我们打开根目录却看到下面什么都没有,这是因为创建失败了(系统没有这个权限)
换到其他路径下面就可以了。
对于我们前面的ps来说,ps的作用就是打来ls /proc 并且进行文本分析
/proc 目录不是磁盘级的文件,所以当系统重启的时候,这个目录下面也会刷新,进程相关的属性以文件的形式展现出来,不同担心影响系统的效率。
接下来,我们说一下ppid。
在linux系统重新启动之后,创建任何进程的时候,新创建的进程都是由父进程创建的。
我们通过下面这个指令来获取父进程:
int getppid();
比如说,我们创建下面的这个代码并形成进程。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{/*chdir("/");FILE *fp=fopen("log.txt","w");if(fp==NULL){}*/pid_t ppid =getppid();pid_t id = getpid();while(1){printf("hello bit,i am a process pid: %d,ppid: %d\n",id,ppid);sleep(1);}return 0;
}
我们可以很明显的发现的是每一次pid都会发生变化,但是ppid却一直不发生变化。
在命令行当中,执行命令,执行程序,本质就是bash的进程,创建的子进程,执行我们的代码,这个bash,也叫做命令行解释器,shell,是所有命令行解释器的统称。在LInux当中,一般使用的是bash。
那么一个bash是怎么创建子进程的呢?
使用系统调用来创建进程,比如说,我们执行./程序,其实就是相当于我们告诉系统,我们要执行这个程序。
接下来,我们来见一下子进程的创建。
接口:
fork()
作用是创建一个子进程。
返回-1则失败了,成功的话,给父进程返回子进程的pid,给子进程返回0 。
接下来,我们创建一个程序来创造子进程:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{/*chdir("/");FILE *fp=fopen("log.txt","w");if(fp==NULL){}*/printf("I am a process pid %d ppid %d\n",getpid(),getppid());pid_t id = fork();(void)id;printf("I am a 分支 pid %d ppid %d \n",getpid(),getppid());/*pid_t ppid =getppid();pid_t id = getpid();while(1){printf("hello bit,i am a process pid: %d,ppid: %d\n",id,ppid);sleep(1);}*/return 0;
}
这个是代码的运行结果:
经过fork之后,有了两个执行分支,一个调用printf,另外一个也调用printf,这两个分支之间也是父子关系,一个是父进程(自己),一个子进程(fork创建的),一个父进程是可以创建出多个子进程的,所以进程其实也是树形结构。
我们接下来看下面的这段代码:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int gval=0;int main()
{printf("i am a process, pid: %d,ppid: %d\n",getpid(),getppid());pid_t id=fork();if(id > 0){while(1){printf("我是父进程,pid : %d,ppid:%d,ret id:%d %d\n",getpid(),getppid(),id,gval);gval+=1;sleep(1);}}else if(id==0){while(1){printf("我是子进程,pid : %d,ppid:%d,ret id:%d %d\n",getpid(),getppid(),id,gval);sleep(1);}}return 0;
}
我们可以看到,这两个死循环同时再跑->两个执行流同时在跑,if 和else if同时成立,从fork之后就会有两个进程,分布进行,fork有两个返回值,给父进程返回子进程的pid,给子进程返回0.
一个父进程有多个子进程,但是一个子进程只会有一个父进程
fork函数
fork创建了一个进程,使得原本的一个进程变成了两个呈现父子关系的进程,一般而言,这两个进程之间的代码是会共享的,但是数据不会,数据会各自私有一份。
所以,进程=内核数据结构+代码和数据
我们每次给新进程创建PCB,可以子进程和父进程相比,并没有数据和代码从磁盘继承,只有内核数据结构,所以,系统要求子进程和父进程共享代码,数据是各自私有一份的。
为什么呢?
因为代码具有很强的独立性,多个进程之间运行互不影响,即便是父子,两个进程之间也有很强的独立性。
所以,代码虽然是共享的,但是是只读的,而数据是各自私有一份的。
Pid_t id =fork();
在这个函数当中,
- id是一个变量
- 返回的本质,就是向指定变量进行写入返回值数据
- 打印的本质,就是读取
创建多进程
那么其实有一个很奇怪的点,就是fork怎么会有两个返回值呢?
其实就是因为多了一个进程,也就是多了一个task_struct,一般而言,子进程的task_struct是从父进程拷贝下来的,调整了新的task_struct的部分属性。
所以有一部分属性,父子进程是相同的,代码要指向同一块,但是pid不同。
当fork的时候,fork是一个系统调用,当我们return的时候,它们的核心工作已经做完了,父子进程都已经开始运行了。
return本身就是一行代码
而fork之后代码是会共享的,所以父进程执行一次return,子进程执行一次return。
那么fork之后,是哪个进程先运行呢? 这个其实是不确定的(有OS调度器自主决定)。
但是时间片可能是一个方面的影响因素。
好了,本次的文章就到这里了,我们下次再见。