【Linux】进程概念
https://i-blog.csdnimg.cn/direct/9b5062c1d78b4aefa50c8e311d7843b4.jpeg#pic_center什么是进程?
进程是利用系统中的一个根本概念,它是正在运行的程序的实例。进程不仅仅是代码,还包罗代码执行时所需的资源和状态信息。
简朴来说进程=程序的代码和数据+内核数据布局(内核数据布局用于管理进程的资源和状态等信息)
形貌进程—PCB
由于上面我们说到进程即是内核数据布局加上自己的代码和数据,这里的数据布局在Linux中叫做task_struct,task_struct是PCB的一种。
什么是PCB
PCB是利用系统中用于管理每个进程的重要数据布局。它包含了利用系统需要的全部信息,用来跟踪、控制和调度进程。每个进程都会对应一个唯一的PCB,利用系统通过PCB来辨认和管理进程的状态和资源。
首先我们知道一个程序在运行时都是要先被加载到内存中的,然后加载到内存中之后由CPU进行读取数据。加载到内存中,我们是不是只加载了程序的代码,很显然不是的,如果我们只加载代码和数据,那我们该怎么管理这个进程呢?所以我们还需要一推数据来方便管理这个进程,比如说这个进程的状态,标识符,优先级等等信息,这些信息被合起来创建了一个task_struct,来管理这个进程。
这里展示部门task_struct的代码:
struct task_struct {
/*
* These are the only fields we actually need to create a task:
*/
struct list_head tasks; /* 指向同一进程组中所有任务的列表头 */
pid_t pid; /* 进程ID */
int state; /* 进程状态 */
unsigned int flags; /* 进程标志 */
struct task_struct __rcu *real_parent; /* 父进程PCB指针 */
struct task_struct *parent; /* 父进程PCB指针,即使父进程已退出也不会为NULL */
struct list_head children; /* 子进程链表 */
struct list_head sibling; /* 兄弟进程链表 */
/*
* These fields are here for internal use. They should not be
* touched outside of the scheduler proper.
*/
struct thread_info *thread_info;
struct fs_struct *fs; /* 文件系统结构体 */
struct files_struct *files; /* 打开文件结构体 */
struct signal_struct *signal; /* 进程信号管理结构体 */
struct sighand_struct *sighand; /* 信号处理函数集合 */
struct nsproxy *nsproxy;
struct cred *cred;
struct cred *real_cred;
u64 start_time; /* 进程开始时间 */
struct timer_list real_timer;
struct pid_link pids;
struct taskstats *stats; /* 与进程相关的统计信息 */
可以看见管理进程的代码中有很多进程的信息。
https://i-blog.csdnimg.cn/direct/eeae35b0c93f4dba9615f8a99d331a40.png
在task_struct中有一个指针指向下一个进程,还有一个指针指向自己对应的进程
task_struct当中的内容
[*]标示符: 形貌本进程的唯一标示符,用来区别其他进程。
[*]状态: 任务状态,退出代码,退出信号等。
[*]优先级: 相对于其他进程的优先级。
[*]程序计数器: 程序中即将被执行的下一条指令的所在。
[*]内存指针: 包罗程序代码和进程相干数据的指针,还有和其他进程共享的内存块的指针
[*]上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
[*]I/O状态信息: 包罗表现的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
[*]记账信息: 可能包罗处理器时间总和,使用的时钟数总和,时间限制,记账号等。
[*]其他信息
首先我们来说说标识符信息。
标识符
标识符是什么?
https://i-blog.csdnimg.cn/direct/fc907e6150fd4e7bbccfda8e1856bd8c.png
先写一段简朴的死循环代码。
https://i-blog.csdnimg.cn/direct/381cebb5cd23405a85b31ecb737e6fdf.png
用上面指令加管道检察指定进程。
ps ajx | grep myexe
首先,ps ajx是检察全部的进程,背面加上管道就可以检察指定的进程
https://i-blog.csdnimg.cn/direct/f1b8bfce0b9546ef9888a607b8a83c61.png
可以看见检察全部进程的时候第一排是有头部信息的,而我们的没有,所以我们可以把头部信息加上。
ps ajx | head -1 && ps ajx | grep myexe
https://i-blog.csdnimg.cn/direct/07d46bc4f7ad47de95581220c5eef4f1.png
这段指令就可以使得检察指定进程的同时趁便加上头部信息了。
可以看见,这里的头部信息有很多,pid就是我们所说的标识符,标识符具有唯一性,可以唯一的确定一个进程,意思就是我们可以通过标识符来查找进程。
https://i-blog.csdnimg.cn/direct/4b7744ac1eee4b6794308adeb30c9a7d.png
可以看见确实可以用pid来检察指定进程,这里可以看见多出来一个bash进程,通过观察可以看见myexe的ppid和bash的pid是雷同的,可以发现bash和myexe是父子进程,bash是命令解释器,myexe是bash的子进程。意思就是说在启动myexe的时候,bash启动了一个myexe的子进程。
ppid
ppid是父进程(parent pid)可以这样理解
直到怎么检察进程之后,我们应该如何结束进程呢?
第一种方式:Ctrl+c
第二种方式:kill+进程标识符
这两种方式都可以结束进程。
我们已经知道了bash会创建一个子进程来执行我们的命令,那么我们该如何手动创建一个子进程呢?
https://i-blog.csdnimg.cn/direct/9ec5cbdf26c84aa59adab36e83de8a33.png
通过上面的函数fork()可以手动创建一个子进程。
https://i-blog.csdnimg.cn/direct/1bf9f0c5b61c4409be70548da1263021.png
可以看见创建成功会给父进程返回子进程的pid,给子进程返回0,如果创建失败会返回-1。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 pid_t id=fork();
7 while(1)
8 {
9 if(id>0)
10 {
11 printf("我是父进程,pid:%d ,ppid:%d\n",getpid(),getppid());
12 sleep(1);
13 }
14 else if(id==0)
15 {
16 printf("我是子进程,pid:%d ,ppid:%d\n",getpid(),getppid());
17 sleep(1);
18 }
19 }
20 return 0;
21 }
上面代码可以创建子进程,我们来看看现象。
运行结果看看现象:
https://i-blog.csdnimg.cn/direct/203075688bf64c21a4eb26785f4e1d16.png
可以看见子进程和父进程都打印了,看看上面代码,明显这是一个if和else if为什么会两个条件同时成立呢?
因为这里创建了一个子进程,子进程和父进程共享同一份代码,但是数据是私有的,所以会产生这样的结果,我们来验证一下是不是。
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int gval;
5
6 int main()
7 {
8 pid_t id=fork();
9 while(1)
10 {
11 if(id>0)
12 {
13 printf("我是父进程,pid:%d ,ppid:%d ,id:%d ,gval:%d\n",getpid(),getppid(),id,gval);
14 sleep(1);
15 }
16 else if(id==0)
17 {
18 printf("我是子进程,pid:%d ,ppid:%d ,id:%d ,gval:%d\n",getpid(),getppid(),id,gval);
19 gaval++;
20 sleep(1);
21 }
22 }
23 return 0;
24 }
首先这里创建一个全局变量,子进程负责++和读取,但是父进程只读取,如果是同一份数据的话,那么全局变量两个应该打出来是同一个值,这里我们运行验证一下。
https://i-blog.csdnimg.cn/direct/36eaae6abcac4e468b2437f986321148.png
可以看见只有子进程的++了,父进程并没有++,可以看见两个进程的数据是私有的,这里我们可以得出一个结论:两个进程之间是具有高度独立性的。
验证完这个之后,我们该如何创建多进程呢?
多进程创建
1 #include<iostream>
2 #include<vector>
3 #include<unistd.h>
4 #include<sys/types.h>
5 using namespace std;
6
7 int num=10;
8
9 void SubProcess()
10 {
11 while(true)
12 {
13 cout<<"I am sub process, pid:"<<getpid()<<", ppid:"<<getppid()<<endl;
14 sleep(1);
15 }
16 }
17
18 int main()
19 {
20 vector<int> allchild;
21 for(int i=0;i<num;i++)
22 {
23 pid_t id=fork();
24 //子进程进入
25 if(id==0)
26 {
27 SubProcess();
28 }
29 //到这里只有父进程
30 allchild.push_back(id);
31 }
32 cout<<endl;
33 for(auto child:allchild)
34 {
35 cout<<child<<' ';
36 }
37 cout<<endl;
38 while(true)
39 {
40 cout<<"我是父进程:pid:"<<getpid()<<" ,ppid: "<<getppid()<<endl;
41 sleep(1);
42 }
43
44 return 0;
45 }
https://i-blog.csdnimg.cn/direct/d31f7a774de0425eb2f2f62e59271648.png
看看对应信息确实对应起来了
https://i-blog.csdnimg.cn/direct/5cb3b80d0b78498396856c7f8592ba9e.png
可以看见确实创建了十个进程。
总结
本文从进程的根本概念入手,介绍了进程的构成布局,尤其是PCB(进程控制块)的作用。通过分析 task_struct 的内容,我们了解了进程在内核中的重要数据布局如何资助管理其状态和资源。随后,我们探讨了多进程的创建过程,并通过代码实例展示了多进程的实现。总的来说,进程是利用系统管理资源的关键单元,深入理解其布局和机制对于系统级编程至关重要。
通过这篇文章,渴望读者可以或许对进程的内部运作和管理有更加清晰的认识,为日后深入学习利用系统或编写并发程序打下底子。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]