linux系统学习之路(一)

进程和程序

什么是程序?
编译好的二进制文件
什么是进程?
运行着的程序。
站在程序员的角度:运行一系列指令的过程。
站在操作系统角度:分配内存单元的基本单位。
区别:

程序占用磁盘,不占用系统资源。
内存占用系统资源。
一个程序对应多个进程,一个进程对应一个程序。
程序没有生命周期,进程有生命周期。

单道程序和多道程序

linux系统学习之路(一)

MMU的作用

1.虚拟内存到内存的映射
2.修改内存访问级别
linux系统学习之路(一)
用户空间映射的物理内存是独立的。

PCB的概念

每个进程在内核中都有一个进程控制块来维护进程相关的信息,linux内核的进程控制块是task_struct结构体。
通过这个命令进行查找:sudo grep -rn “struct task_struct {” /usr/
光标停留在{上,按%,可以到结构体的结尾,400多行。
内部成员定义:

1.进程id.系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
2.进程的状态,有就绪,运行,挂起,停止等状态。
3.进程切换时需要保存和恢复的一些CPU寄存器。
4.描述虚拟地址空间的信息
5.描述控制终端的信息
6.当前工作目录
7.umask掩码
8.文件描述符表,包含很多指向file结构体的指针。
9.和信号相关的信息
10.用户id和组id
11.会话和进程组
12.进程可以使用的资源上限

获取环境变量

通过env命令查看环境变量。
查看具体环境变量echo $变量名
常用环境变量
PATH:
linux系统学习之路(一)
linux系统学习之路(一)
环境变量作用
linux系统学习之路(一)
getenv函数
获取环境变量的值
char getenv(const char name);成功:返回环境变量的值;
失败:NULL(name不存在)

进程控制函数

linux系统学习之路(一)
fork函数
创建一个子进程:
pid_t fork(void);失败返回-1;成功返回:(1)父进程返回子进程的ID(非负)(2)子进程返回0
pid_t类型表示进程ID,但为了表示-1,它是有符号整型,(0不是有效进程ID,init最小,为1)注意返回值,不是fork函数返回能返回两个值,而是fork后,fork函数变为两个,父子需各自返回一个。
获得pid,进程id,获得当前进程
pid_t getpid(void);
获得当前进程父进程的id
pid_t getppid(void);
linux系统学习之路(一)
可以看到End打印两次。
linux系统学习之路(一)
linux系统学习之路(一)
打印结果:
父进程先s了,但是子进程此时成为孤儿进程。
linux系统学习之路(一)
加睡眠时间
linux系统学习之路(一)
打印结果:
linux系统学习之路(一)
linux系统学习之路(一)

进程控制的命令

linux系统学习之路(一)
linux系统学习之路(一)
linux系统学习之路(一)
查看进程信息

ps aux
ps ajx --可以追溯进程之间的血缘关系

kill

kill -l 查看信号相关的信息
给进程发送一个信号
SIGKILL 9号信号
kill -SIGKILL pid --杀死进程

创建n个子进程

linux系统学习之路(一)
查看进程数
linux系统学习之路(一)
循环创建n个子进程。如果在循环中子进程不中断,将出现问题,子进程会在循环中继续创建子进程。
linux系统学习之路(一)
################分割线##############################
linux系统学习之路(一)
linux系统学习之路(一)

循环创建n个子进程控制顺序

让这些子进程顺序退出,然后父进程最后退出。
linux系统学习之路(一)
linux系统学习之路(一)

父子进程共享的内容

linux系统学习之路(一)
出现这种结果是因为printf有缓冲区机制,没有加\n或者没有刷新。则printf内容没有打印出来。fork之后,子进程的缓冲区也有这个内容。
linux系统学习之路(一)

父子进程共享的内容

父子进程之间在fork后,有哪些相同,哪些不同之处呢?
刚fork之后:
父子相同处:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式。
父子不同处:1.进程ID 、2.fork返回值、 3.父进程ID 4.进程运行时间、5.闹钟(定时器)6.信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但PID不同。真的是每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存空间。
linux系统学习之路(一)

即使是父子进程,在写时候也不共享全局变量

linux系统学习之路(一)
linux系统学习之路(一)
出现箭头所指的是因为shell创建子进程,在发现子进程停止了,就回来了,把终端打印一遍,但不知道还有孙进程。

exec函数族

fork:创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往会调用一种exec函数以执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行,调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
作用是执行其他程序:

int execl(const char* path,const char arg,…/ * (char)NULL*/)
下边这个函数声明表示,执行程序的时候,使用PATH环境变量, 执行的 程 序可以不用加路径。
int execlp(const char* file,const char arg,…/ * (char)NULL*/);

file 要执行的程序
arg 参数列表

参数列表最后需要一个NULL作为结尾,哨兵作用
返回值:只有有失败才返回。

参数要从arg[0]开始,argv[0]是程序本身,实际上就是一个占位
linux系统学习之路(一)
打印结果:
linux系统学习之路(一)
linux系统学习之路(一)
linux系统学习之路(一)
这个打印结果不会打印printf内容,因为没有回来。
linux系统学习之路(一)
linux系统学习之路(一)

孤儿进程和僵尸进程

一次wait或waitpid调用只能清理一个进程,清理多个子进程应使用循环。
孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
僵尸进程:即子进程先于父进程退出后,子进程的PCB需要其父进程释放,但是父进程并没有释放子进程的PCB,这样的子进程就称为僵尸进程,僵尸进程实际上是一个已经死掉的进程。

孤儿进程实现:
linux系统学习之路(一)
僵尸进程:
linux系统学习之路(一)
如何回收僵尸进程:kill 父进程,init领养,负责回收。

wait函数简单使用和说明

特别注意,僵尸进程是不能使用kill命令清除掉的。因为kill命令只是用来终止进程的而僵尸进程已经终止。

wait函数

wait函数
int wait(int *status)
函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这些进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,因为shell是它的父进程,当它终止时shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:
pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

作用:

1.阻塞等待。
2.回收子进程资源
3.查看死亡原因

pid_t wait(int *status);

status 传出参数
返回值

成功返回终止的子进程ID
失败返回 -1

linux系统学习之路(一)
子进程死亡原因:

正常死亡WIFEXITED

如果WIFEXITED为真,使用WEXITSTATUS得到退出状态。

非正常死亡WIFEXITED

如果WIFSIGNALED为真,使用WTERMSIG得到信号。

如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:

1,WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。

(请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数–指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)

2, WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

linux系统学习之路(一)

linux系统学习之路(一)

waitpid回收子进程

进程组概念:
linux系统学习之路(一)
pid_t waitpid(pid_t pid, int *status, int options)

pid

  1. pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程>>运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去
  2. pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和>>wait的作用一模一样。
    3.pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
    4.pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options:如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。如果使用了0则和wait一样。
返回值:
1、当正常返回的时候,waitpid返回收集到的子进程的进程ID;
2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;(没有子进程)
linux系统学习之路(一)
linux系统学习之路(一)
产生僵尸进程没有回收成功。linux系统学习之路(一)
对代码进行修改:
linux系统学习之路(一)
linux系统学习之路(一)
没有产生僵尸进程
linux系统学习之路(一)

用wait回收多个子进程

linux系统学习之路(一)
linux系统学习之路(一)

waitpid回收多个子进程

linux系统学习之路(一)
linux系统学习之路(一)
linux系统学习之路(一)