linux中的进程

2019-07-14 08:18发布

#什么是进程
进程就是一个正在执行的程序
从内核来看,进程是一个担当分配系统资源的实体,当程序被计算机执行,那么就可以称之为进程 #linux中的进程
进程信息被放在一个叫做进程控制块的数据结构中,也就是PCB(process control block),PCB的本质是一个结构体,每一个进程都有一个PCB,当进程创建PCB就被创建,直至进程销毁。
linux中PCB叫做task_struct,进程创建时它会被装在到内存中,其中包含了进程的各个信息。 #进程信息
标识符:描述进程的唯一标识符,相当于身份证,又叫做PID,他是一个整型数
优先级:进程执行相对于其他程序的优先情况
进程状态:任务状态,运行、暂停、僵死等等
程序计数器:程序中即将被执行的下一条指令地址
内存指针:包含程序代码和进程相关数据的指针,还有和其他进程共享的内存块指针
上下文数据:进程执行时处理器的寄存器中的数据
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息:可能包括处理器时间综合,使用的时钟数总和,时间限制,记账号
其他信息 ##进程状态
运行状态(R)
可中断睡眠状态(S)
不可中断睡眠状态(D)
停止状态(T)
死亡状态(X)
僵尸状态(Z) ##优先级
CPU资源分配的先后顺序
优先级高的程序有优先执行的权利
和优先级有关的进程信息是PRI和NI PRI:代表进程可被执行的有先级
NI:表示该进程的nice值 //查看进程信息 $ ps -l 优先级 PRI的值越小程序优先级越高,PRI值小的会被先执行,PRI值和范围在60~99,一般我们看的话优先级都是80或者85
nice值可以和PRI的值来配合修改优先级
公式:PRI(new) = PRI(old) + nice
nice的取值位-20到19,共40个级别 //样例,把nice值改为-5,优先级就会从80 -5 变成75 $ nice -n -5 ./test #父进程和子进程
##父进程如何创建子进程
操作系统以父进程为模板创建子进程,父子进程代码段相同,但是数据各自独有
fork()返回值起到分流作用,我们用户通过fork()的返回值判断哪个是父进程,哪个是子进程
对于父进程来说,返回值是子进程的PID,对于子进程来说,返回值是0
##父子进程之间的关系
父进程和子进程之间共享代码段和数据段,但当子进程的数据出现变化时,父进程和子进程的数据段就不再指向同一块区域,这个技术叫做写时拷贝。
子进程的PPID就是父进程的PID
##fork举例
创建子进程有两种方式:fork()vfork(),首先看fork()实例 #include #include int main() { int val = 100; pid_t pid = fork(); if (pid < 0) { printf("Create process failed!! "); return -1; } else if (pid == 0) { //子进程的fork()值为0 //修改val的值,观察内存地址是否发生变化 val = 200; printf("Child process, pid->%d ", getpid()); } else { sleep(1); printf("Parent process, pid->%d ", getpid()); } while (1) { printf("Common process, pid->%d, val: %d, val address->%p ", getpid(), val, &val); sleep(1); } return 0; } 代码执行结果
fork
进程观察
fork
fork()的作用就是创建一个子进程,一般fork之后会用if语句进行分流执行不同的代码
在我的代码中我通过if语句分流,在子进程部分修改了val的值,之后执行相同的代码
###解释一下内存地址相同问题
观察代码结果发现子进程的val和父进程的不同,但是两个val的地址却是一样的,这就说明了这两个地址虽然值是一样的,但是他们指向的不是同一块空间,原理如下图,当val的值改变时,数据段就会被复制,子进程就有独立的数据段,在fork之后他们其实是第一种情况,val改变时就变成第二种情况。
父子进程数据段说明
关于虚拟内存,其实每个程序都有一个虚拟内存,其实是个结构体,虚拟的内存中地址由小到大和物理内存的编号一样,不同的是虚拟内存每一个地址通过一个页表映射到物理内存中,这就是虚拟内存的原理,这就解释了为什么地址相同但是值不同的问题了,下图请配合上图一同食用
虚拟内存
##vfork()
fork如果是个孝顺的孩子(子进程),当数据出现不同时,便单独开辟空间来管理数据,那么vfork相对于fork而言就是个逆子,vfork创建的子进程数据直接在父进程修改,就好比同样有个房子,fork另外买一个一模一样的来修改,而vfork则是在原来的房子里面乱搞,肆意破坏。
当vfork出来的子进程执行时,父进程不运行,直到子进程执行结束才继续执行。
代码很简单,修改上面代码为vfork即可
只运行子进程,父进程无输出
vfork
父进程处于不可中断睡眠状态,子进程可中断睡眠状态
vfork #僵尸进程
僵子进程退出时会告诉父进程子进程任务完成怎么样了,如果父进程不关心子进程的退出,那么子进程就会变成僵尸进程,父进程不读取,子进程就一直为僵尸进程,造成内存泄漏。 下面通过代码演示僵尸进程
父进程睡眠30秒,子进程在创建后5秒退出,此时父进程在睡眠,无法获取子进程状态,子进程变为僵尸进程 #include #include #include int main() { pid_t pid = fork(); if (pid < 0) { printf("Create child process error! "); } else if (pid > 0) { printf("This is parent process, pid->%d, begin to sleep! ", getpid()); sleep(30); } else { printf("Child process's pid->%d, begin Z... ", getpid()); sleep(5); exit(EXIT_SUCCESS); } return 0; } zombie
一开始的5秒内两个进程均正常,5秒后子进程退出,父进程没有读取到信息变成了僵尸进程
zombie
##如何避免僵尸进程
让父进程来回收子进程,调用wait()waitpid()函数来关注子进程的状态,通知内父进程回收(wait()的用法之后会另外写一篇博客)
或者让子进程变成孤儿进程由init回收 #孤儿进程
父进程先于子进程退出,那么子进程就成为了孤儿进程,这个孤儿进程将被孤儿院init进程所领养,子进程退出后将由init进程来回收释放资源 孤儿进程举例: #include #include #include int main() { int val = 100; pid_t pid = fork(); if (pid < 0) { printf("Create process failed!! "); return -1; } else if (pid > 0) { printf("This is parent process, it's pid is %d, prepare to exit in 15 seconds! ", getpid()); sleep(15); printf("Parent process exit now! "); exit(0); } printf("This id child process, it's pid is %d ", getpid()); while (1) { sleep(1); } return 0; } 代码演示结果
orphan
查看进程信息看到orphan进程(PID为2958)的PPID由2957变为1,1进程就是linux中的孤儿院——init进程,他收留了orphan进程,所以如果orphan退出将由init回收
orphan