Linux进程浅析(中)
- 进程调度
- 进程标识
- 进程创建
- 父子进程的关系
- 进程链和进程扇
- 孤儿进程
进程调度
第一步:处理内核中的工作
第二步:处理当前进程
第三步:选择进程
1:实时进程
2:普通进程
第四步:进程交换(开销很大)
目前主要运用的是分时系统,CPU会为进程分配时间片,
在时间片内进程需要运行完毕,如果不运行完毕,那么也必须要让出CPU,
task_struct中的调度信息:
策略
轮流策略
先进先出策略
优先权
jiffies变量
实时优先权
计数器
下面是一张从百度图库中找的一张图示,可以很好的看到进程在各种状态中的相互转换,当然,最重要的还是其中运用到的相关调度策略
单cpu同一时刻仅仅只是一个进程在使用(并发方式运行)
多cpu可以支持多个进程同时运行(并行方式运行)
进程标识
#include<unistd.h>
#include<sys/types.h>
pid_t getpid(void);获得当前进程ID
uid_t getuid(void);获得当前进程的实际用户ID(执行的时候如果使用sudo,实际用户为root)
uid_t geteuid(void);获得当前进程的有效用户ID(登录用户)
gid_t getgid(void);获得当前进程的用户组ID
pid_t getppid(void);获得当前进程的父进程ID
pid_t getpgrp(void);获得当前进程所在的进程组ID
pid_t getpgid(pid_t pid);获得当前进程ID为pid的进程所在的进程组ID
注意:getpid和getuid,geteuid以及getppid相对使用的频率稍微会高一点点
简单用过代码来打印一下这些用户的ID
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char *argv[]){
//获取当前进程的ID
printf("getpid:%d\n",getpid());
//获取当前进程的实际用户的ID
printf("getuid:%d\n",getuid());
//获取当前进程的有效用户的ID
printf("geteuid%d\n",geteuid());
//获取当前进程的有效用户ID
printf("getgid%d\n",getgid());
//获取当前进程的父进程ID
printf("getppid:%d\n",getppid());
//获取当前进程所在的进程组ID
printf("getpgrp:%d\n",getpgrp());
//获取当前进程ID所在的进程组ID
printf("getpgid:%d\n",getpgid(getpid()));
return 0;
}
这里 的代码是非常简单的,也就是简单打印一下对应的ID的值,需要注意的是getuid和geteuid的区别,一个(getuid)是进程的实际用户ID,而geteuid则是进程的有效用户的ID
进程创建:
记得刚刚开始接触开发的时候,项目组老大当时让我去了解了一些关于ndk开发相关的东西,想要了解ndk首先要懂一点c吧。而android系统的内核有是linux的,所以那会经常会跟我提fork进程,fork进程,最开始听到这一个概念的时候,完全蒙圈了,这是啥意思啊?完全没懂,现在才明白。fork函数是linux内核提供的一个创建子进程的一个系统调用。现在我们来了解一下什么是fork函数(进程创建)
fork和vfork函数:
#include<unistd.h>
#include<sys/types.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程ID,出错为-1;
pid_t vfork(void);
返回:子进程 为0,父进程中为子进程ID,出错为-
fork创建的新进程被称为子进程,该函数被调用一次,但,次
两次返回的区别就是:在子进程中的返回值是0;而在父进程中的返回值则是新子进程ID
创建子进程,父子进程哪个先运行根据系统调度且复制父进程的内存空间
vfork创建子进程,但是子进程先运行且不复制父进程内存空间
vfork是让子进程先去执行
注意:
1.通过fork出来的子进程与主进程的调用是没有先后次序的,可能是子进程一会先执行,也可能是主进程一会先去执行
2.通过vfork产生的不会复制父进程内存空间,并且子进程先执行
3. fork返回大于0的时候,是主进程执行片段。等于0是子进程执行片段,小于0的时候fork出错。
下面我们就来fork一个我们自己的子进程吧:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
int main(int argc,char * argv[]){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error\n");
exit(EXIT_FAILURE);
}else if(pid > 0){
//父进程执行的片段,返回的是子进程的ID
printf("this is a parent process:%d,and return pid:%d\n",getpid(),pid);
}else {
//如果是子进程的话,那么返回的就是0
printf("this is a child process:%d,and parent pid:%d\n",getpid(),getppid());
}
//fork后面。是父子进程共同执行的片段
printf("getpid:%d,getppid:%d\n",getpid(),getppid());
return 0;
}
在我终端上运行的结果为:
可以看出终端的id为3648,而父进程fork出来的子进程id为4958,而父进程自己的id为4959,从而可以看出其关系
而在上面其实提到了,父子进程的调用是无序的,也就是很有可能先执行父进程,也有可能先执行子进程。它是根据系统的调度来完成的。所以需要注意
也通过一个案例来简单了解下,在上面的代码上做了稍稍的修改:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
int main(int argc,char * argv[]){
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error\n");
exit(EXIT_FAILURE);
}else if(pid > 0){
//父进程执行的片段,返回的是子进程的ID
int i = 0;
for(i = 0;i<10;i++){
printf("parent:getpid:%d,count:%d\n",getpid(),i);
sleep(1);
}
}else {
//如果是子进程的话,那么返回的就是0
int j = 0;
for(j = 0;j < 10;j++){
printf("child:getpid:%d,count:%d\n",getpid(),j);
sleep(1);
}
}
//fork后面。是父子进程共同执行的片段
return 0;
}
其实这种调度过程类似于线程的并发一样,可能在大多数情况下,都是父进程先执行,但是当执行的次数越多的时候,出现的差异的概率也就会增大。所以这一点父子进程调度的顺序一定是要注意的,在程序开发的时候尤其需要做一点差异处理
父子进程的关系
1:父进程fork出一个子进程,子进程会继承父进程的相关信息; (父进程在拷贝的时候将4G的内存全部拷贝给了子进程)
子进程的继承属性:
用户信息和权限
目录信息
信号信息
环境变量
共享存储段
资源限制
堆,栈和数据断,共享代码段
2:子进程的特有属性:
进程ID,锁信息,运行时间,未决信号
3:操作文件时内核的结构变化
子进程只会继承父进程的文件描述表,不继承但共享文件表项和i-node
父进程创建一个子进程后,文件表象中的引用计数器会变成2,
当父进程作close操作后,计数器减1,子进程还是可以使用文件表项
只有当计数器为0时才会释放文件表项;
4:物理内存映射
虚拟内存会映射一段物理内存,而父子进程的物理内存空间是不一样的;
子进程的虚拟内存中的数据大多数都是从父进程中直接拷贝过去的,但是同样也存在子进程的差异项,但是其对应的物理内存却有很大的差别
上代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int num_a = 10;
int main(int agrc,char *argv[]){
int num_b = 10;
static int num_c = 10;
pid_t pid = fork();
if(pid < 0){
perror("fork error\n");
exit(EXIT_FAILURE);
}else if(pid > 0){
//在父进程中去做这样的修改
num_a = 20;
num_b = 20;
num_c = 20;
printf("parent id:%d\n",getpid());
}else{
//在子进程中去做这样的修改
num_a = 30;
num_b = 30;
num_c = 30;
printf("child id:%d\n",getpid());
}
printf("getpid:%d,num_a:%d,num_b:%d,num_c:%d\n",getpid(),num_a,num_b,num_c);
printf("num_a:%p,num_b:%p,num_c:%p\n",&num_a,&num_b,&num_c);
return 0;
}
结果:
parent id:16163
getpid:16163,num_a:20,num_b:20,num_c:20
num_a:0x601060,num_b:0x7ffe1dffc700,num_c:0x601064
child id:16164
getpid:16164,num_a:30,num_b:30,num_c:30
num_a:0x601060,num_b:0x7ffe1dffc700,num_c:0x601064
从程序中可以看出来,num_a,num_b,num_c都是在父亲进程中的,但是在fork子进程之后,子进程会将父亲进程的 内存结构大多数原样不懂的拷贝到子进程中,就连最后的内存地址也是一样拷贝,所以看到,父子进程中打印出来的地址其实都是一样的,但是其并不是在同一个虚拟内存中的地址。还有就是他们的虚拟内存对应的物理内存的存储结构就有很大的差异了
还有一个很重要的父子进程的需要注意的地方就是父子进程在操作文件的时候需要注意的,下面的Code就是父子进程对文件的操作过程:从里面可以差异点:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<string.h>
int main(int argc,char * argv[]){
if(argc < 2){
fprintf();
exit(EXIT_FAILURE);
}
int fd = open(argv[1],O_WRONLY);
pid_t pid = fork();
if(pid <0){
perror("fork error");
exit(EXIT_FAILURE);
}else if(pid > 0 ){
//父进程将文件描述符定位到文件尾部
if(lseek(fd,0L,SEEK_END) < 0){
perror("lseek error\n");
exit(1);
}
}else {
//子进程去追加字符串
sleep(2);
char *str = "hello world";
ssize_t size = strlen(str) * sizeof(char);
//子进程会复制父进程的文件描述符号,共享文件表项目和i节点,当父进程close之后,子进程同样可以对文件进行操作。只是引用计数器会做-1操作,当引用技术器为0的时候,这个时候文件的数据结构就会去释放
if(write(fd,str,size) != size){
perror("write error\n");
exit(1);
}
}
//父进程关闭之后,对子进程的操作其实不构成影响
close(fd);
return 0;
}
进程链和进程扇
图说进程链和进程扇
进程链:
进程链:子进程再去fork一个进程,这样一层一层继续fork下去的叫做进程链;
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char * argv[]){
int fork_times = 0;
pid_t pid;
if(argc < 2){
perror("缺少参数\n");
fork_times = 2;
}else{
fork_times = atoi(argv[1]);
}
int i = 1;
for(i = 1 ; i< fork_times;i++){
pid = fork();
if(pid < 0){
perror("fork error\n");
exit(1);
}else if(pid > 0){
//如果是父进程的话,那么就直接退出
break;
}else {
//如果是子进程的话,子进程会继续进行fork
}
}
printf("getpid:%d,getppid:%d\n",getpid(),getppid());
sleep(1);
return 0;
}
进程扇:
进程扇:子进程去fork两个子进程,那么这两个子进程再继续去往下fork子进程,这个我们叫做进程扇
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char * argv[]){
int fork_times = 0;
pid_t pid;
if(argc < 2){
perror("缺少参数\n");
fork_times = 2;
}else{
fork_times = atoi(argv[1]);
}
int i = 1;
for(i = 1 ; i< fork_times;i++){
pid = fork();
if(pid < 0){
perror("fork error\n");
exit(1);
}else if(pid == 0){
//如果是子进程的话,那么就直接退出
break;
}else {
//如果是父亲进程的话,父进程会继续进行fork
}
}
printf("getpid:%d,getppid:%d\n",getpid(),getppid());
sleep(1);
return 0;
}
代码都是可以直接进行run的,可以根据其打印出来的进程id来追踪到
孤儿进程
孤儿进程的概念:父进程结束,子进程未结束,子进程由于失去了父亲,变成了孤儿一样,被送到了领养院,由init进程负责领养
父进程结束,子进程就成为孤儿进程,会由1号进程(init进程)领养
最简单的方法就是在fork的时候,把父进程exit掉,然后把子进程设一个死循环,就可以看到了,子进程后续会被init进程给直接领养。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc,char * argv[]){
pid_t pid;
pid = fork();
if(pid <0){
perror("fork error\n");
exit(EXIT_FAILURE);
}else if(pid > 0){
printf("%d deaded:\n",getpid());
exit(EXIT_FAILURE);
}else{
sleep(4);
printf("getpid:%d,getppid:%d\n",getpid(),getppid());
}
return 0;
}
在ubuntu16.04中,好像是被1482这个进程领养了。后面再继续研究下。好像内核改变了点东西
以上就是关于linux进程相关的浅析,可能写的相对来说也比较简单。希望对有兴趣的童鞋能够有点帮助,代码都是可以直接进行run的,希望有错误的地方可以及时指出来。