操作系统:进程

目录

1.进程

1.1.进程的基本概念

1.2.进程控制块(PCB)

1.3.进程与PCB

1.4.进程的其他知识 

1.4.1.进程与父进程

1.4.2.Linux中进程的创建方式 

1.4.3.进程详细信息查看


1.进程

1.1.进程的基本概念

*课本概念:程序的一个执行实例,正在执行的程序等

*内核观点:担当分配系统资源(CPU时间,内存)的实体。

我们在实际使用计算机的场景中,操作系统会同时运行多个程序,也就是拥有多个进程,我们从操作系统的思想“先描述再组织”中可以知道,为了便于管理,就需要描述个体程序,然后用数据结构串联,这里也就是进程和程序的差别。 

用人话来说:进程是 可执行程序 加上 内核数据结构(PCB) 

1.2.进程控制块(PCB)

*进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

*课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct

 并且我们知道操作系统是c语言实现的,那么我们可以大致建模task_struct

// 对进程的描述
struct task_stcuct
{
	// ... 标识符、状态、优先级
	// ... 下一个进程的地址
	// ... 进程对应代码、数据的虚拟地址
    // ... 等等
};

管理进程的时候对该进程没关系,而只关乎进程的信息。进程对应的PCB作为操作系统便于管理的一个个体单位。

task_struct的串联

如图PCB的串联并不是直接用PCB设置内部指针来实现的,而是通过套上一个list结构体,在通过list里面的指针来进行连接,这样子会不会显得比较麻烦?但是我们想了想,如果只用内部指针,就如同一个一维的链表一样,即某个PCB只在一条链表上。实际场景中,某个PCB可能在多个链表中,甚至队列中、各种其他结构中!

那么如何找到task_struct的首地址呢?答:通过偏移量的计算来查找,对于上图就是

task_struct_current = (task_struct*)(int)&list - (int)&(task_struct*)0->list

1.3.进程与PCB

我们已经大概知道了进程与PCB,那么在实际上进程和PCB是如何配合的呢?接下来我们用一个程序的场景来描述一下。

当我们在运行多个程序时,即需要往内存中加载多个进程,就需要有多个PCB来对应各自的进程这样子能够有序的管理,并且操作系统会形成PCB链表。

那么最终我们发现CPU在运算时,直接读取PCB的信息找到进程再进行程序的运行。然后通过PCB链表进行下一个程序的运算,所以对于进程的管理最终变成了对链表的增删查改

1.4.进程的其他知识 

1.4.1.进程与父进程

如图,我们知道PID是当前进程的代号,PPID是当前进程父进程的代号,当我们在命令行中不断地终止再启用某个进程,会发现PID会改变,PPID却不变,这是为什么呢?首先进程是动态的,所以不同时间片下进程的代号分配会不一致,但是命令行的进程的父进程是固定的,我们通过过滤可以查看进程21070下

发现这个进程是bash,即为命令行解释器,那么有bash是命令行进程的父亲

1.4.2.Linux中进程的创建方式 

创建一个进程的,就是想操作系统中申请内存,保持当前进程的可执行程序和task_struct对象,并将tast_struct对象添加到进程列表中。

1.命令行中直接启动进程            ---通过bash手动创建

2.通过代码来进行进程创建         ---通过fork()函数自动创建

!!!另外启动进程,本质上是创建进程,一般是由父进程来创建。

!!! 通过系统调用创建进程---fork()函数 

fork()函数作用:创造一个子进程

*fork有两个返回值,给子进程返回0,给父进程返回进程PID

*父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝)

  int main()
{
	int ret = fork();
	while(1)
    {
		if(ret < 0)
		{
		   perror("fork");    
		   return 1;
		}
		else if(ret == 0)
		{ //child
			printf("I am child, pid: %d,  ppid: %d, ret: %d\n", getpid(), getppid(), ret);
		}
		else
		{ //father
           printf("I am father, pid: %d, ppid: %d ret: %d\n", getpid(), getppid(),ret);
		}
		sleep(1);
     }
    sleep(1);
	return 0;
  }

这里当我们打印分别打印父子进程的&id会有一个地址对应两个值,这里我们在后续的进程地址空间会解释,就不在这用大篇幅说了!

这份代码体现了fork()函数能够创建两个进程,通过先创好的父进程,再创建子进程

我们在通过Linux的指令查看进程信息,发现一个fork()确实可以实现两个进程。

总结:

*子进程可以有多个,父进程只能有一个!父进程通过子进程标识来控制子进程。

*fork()函数前的代码由父进程执行,fork()后的代码由父子进程各自执行,也就是实现分流!

 这时我们开始思考为什么要创建子进程?试想一个场景,当我们在看视频时本质上就是加载数据,也就是需要边下载边播放,那么这个就是两部分操作独立进行,而通过fork()函数后,父子进程独立执行分流向各自的代码块进行操作。那么不就可以实现我们生活中的一些场景吗?

再结合我们进程会有相应的PCB,那么创建子进程后会以父进程为模版,为子进程创建PCB,并且和父进程共享代码和数据(当数据修改时,需要进行写时拷贝,各自拥有一份,防止破坏进程之间的独立性),并且在不同的时间片各自被CPU调度,继续往后执行,这里也体现了进程的独立性。

因为fork()中拥有两个进程,所以最后也return两个值,即fork()拥有两个返回值

1.4.3.进程详细信息查看

当我们运行进程时,通过PID我们可以找到该进程的临时文件夹,可以看到里面的信息

这里我们可以看到进程对应的exe文件的位置,就在跟code所在的文件夹test中,也就是在cwd中。那么cwd是什么呢?cwd是当前工作目录,因为进程启动时会找到当前工作目录,默认情况下的路径。这时我们联想到当初C语言学习中,文件操作相关函数,当没有文件时,fopen()会创建一个文件,恰好在当前工作目录下。

当我们杀掉这个进程时,PID为13954对应的临时文件夹就被释放了

讲到这里对于进程的概念我们也了解了差不多了, 

  • 27
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值