本文是对于进程概念的知识总结。
以上就是进程的基本概念,那进程就是可执行程序吗?答案为不是。为什么?那我们来进行如下的推导。
操作系统内可能会同时存在非常多的“进程”,那操作系统要不要管理所有的“进程”?要管理。那操作系统如何管理?先描述,再组织(对事物重要的属性描述成一个集合,再对该集合选用合适的数据结构,对该数据结构进行增删查改)。那要描述的对象肯定是“进程”,而操作系统是用C语言写的,也就是对“进程”的描述会形成struct XXX类型(该类型内部包含“进程的各种属性”),再以双向链表的这种数据结构对struct XXX类型对象进行管理,这就形成了进程的PCB(process control block)。
那么现在什么是进程?进程 = 可执行程序 + 内核级别的数据结构(PCB)。其中PCB方便OS对进程的管理(对进程的管理变为对双向链表的增删查改)。
在上图,struct XXX对象中的lis成员,可以通过类似C语言 offset 宏[类似于 (int)&list - (int)(struct XXX*)0->list ]来指向struct XXX的首地址。
可以在内核数据结构里找到PCB,所有运行在系统里的进程都以task_struct链表的形式存在内核里
一个大佬写的文章Linux中进程控制块PCB-------task_struct结构体结构
进程信息可以通过/proc系统文件夹查看,该目录为动态目录结构,存放所有存在的进程,目录的名称就是这个进程的id。
如我们启动如下程序,在/proc目录中查看该进程的信息
通过ps -axj | head -1 && ps -axj | grep mybin查看到该进程的pid为30725
再通过ll /proc/30725查看进程信息
其中cwd就是当前工作目录(默认情况下,进程启动所处的路径),可以通过chdir()系统调用来改变
大多数进程信息同样可以使用top和ps这些用户级工具来获取。
一般ps -axj 配合 grep指令使用。如ps -axj | grep 程序名
我们可以通过getpid,getppid来获取进程的pid(进程id),ppid(父进程id)。
我们运行下面代码:
其结果为:
我们可以知道该进程的pid为6993,ppid为5213。
这里有个小问题,如果我们多次运行该程序(不关闭shell),我们会发现其ppid的值是不变的,那ppid是谁的pid呢?
运行该指令ps -axj | head -1 && ps -axj | grep 5213
我们可以明显的发型bash的pid为5213。所以在命令行中执行的进程,父进程一般是命令行解释器
由上图,我们可以通过fork来创建新的进程,对于父进程,其返回子进程的pid;对于子进程,其返回0;如果创建进程失败,其返回-1。
我们运用如下代码:
其结果为:
从运行结果而言,我们可以通过fork创建一个新的进程,父子进程都执行fork之后的代码;if的两个分支也都被执行,这表示一个函数有两个返回值?
fork创建子进程,系统中会多出一个子进程。
1.以父进程为模版,为子进程创建PCB
2.创建的子进程是没有代码和数据的,目前和父进程共享代码和数据。
因为父子进程共享代码,所以fork之后,父子进程会执行一样的代码,fork之前的代码,父子进程也都能看到。
那父子进程之间是如何保证进程的独立性?
代码本身是只读的,父子进程都不会影响,所以代码是可以共享的。但父子进程对数据是会修改的,当父子进程对数据进行写入,会发生写时拷贝,来保证进程的数据是独立的。
我们先想一个问题,如果一个函数已经执行到return语句了,那么该函数的核心工作做完了吗?
答案是:做完了。那不也就说明fork函数在执行到return语句时,子进程已经创建出来了,而fork函数之后(子进程创建出来后),代码共享,那return语句不也共享了吗。那不就是父进程被调度,就要执行return,子进程被调度,就有执行return。
真实情况是,操作系统是通过一些寄存器做到返回值返回两次。
在我们人类血缘关系中,父与子的比例永远是1 : n,也就是说一位孩子只能有一个父亲,一位父亲可以有多个孩子。换而言之,对于父进程,我们需要唯一标识子进程的标识符(pid)来区分子进程,对于子进程,我们只有一个父进程,不需要特殊表示,我们只需要表示子进程创建成功即可。
创建完成子进程后,系统的其它进程,父进程,子进程接下来要被调度执行。当父子进程的PCB都被创建并在运行队列中排队的时候,那个进程的PCB先被选择调度,哪个进程就先运行。但这是由操作系统决定的,由各自PCB中的调度信息(时间片,优先级等) + 调度算法共同决定。我们无法确定
以上就是我对于进程概念知识的总结。感谢支持!!!