进程是指在系统中正在运行的一个应用程序,程序一旦运行就是进程。
进程是系统进行资源分配的独立实体, 且每个进程拥有独立的地址空间。
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。Linux操作系统下的进程控制块是:task_struct
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
其内容主要包括:
为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
kernel源代码里定义的进程状态:
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用)没有读取到子进程退出的返回代码时就会产生僵尸进程,僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
另外,还有一种进程叫做孤儿进程:父进程先退出,子进程就称之为“孤儿进程”,孤儿进程被1号init进程(就是操作系统)领养,也由init进程回收。
ps -l //查看进程相关信息
UID : 代表执行者的身份
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
nice值其表示为进程可被执行的优先级的修正数值,PRI值越小越快被执行,那么加入nice值后,将会使得PRI变为:PRI(new)=PRI(old)+nice,nice其取值范围是-20至19,一共40个级别。
用top命令可以更改已存在进程的nice:输入top,进入top后按“r”–>输入进程PID–>输入nice值
竞争性:
系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高
效完成任务,更合理竞争相关资源,便具有了优先级。
独立性:
多进程运行,需要独享各种资源,多进程运行期间互不干扰。
并行:
多个进程在多个CPU下分别,同时进行运行,这称之为并行。
并发:
多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为
并发。
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数,例如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。
以下是一些常见的环境变量:
以下是环境变量相关命令:
echo $NAME //NAME:你的环境变量名称
export 设置一个新的环境变量
env 显示所有环境变量
unset 清除环境变量
set 显示本地定义的shell变量和环境变量
当C/C++程序运行起来时,每个进程(正在运行的程序的一种抽象)看到的内存是一致的,就好像每个进程都在独占的使用主存一样,称为虚拟地址空间。如下:
为什么说它是虚拟的呢,因为其并不是对应的物理内存的相应位置。
来看一下这段代码的结果:
我们可以发现,父子进程的flag变量对应的地址是一样的,但是值却不一样?
上面说到过,进程地址空间是虚拟的,而通过操作系统可以将虚拟地址映射到对应的物理地址上。而在进程运行时,会有一个进程控制块来管理进程,同时进程控制块维护了一个结构体,结构体中包含了对应虚拟内存各种区域的边界,这时就好像虚拟内存是真的存在一样。但其实它要通过页表,将对应的虚拟内存地址映射到物理地址上。
所以,虽然上述两个进程的虚拟地址是一样的,但是其页表的映射是不同的,才能将不同的数据映射到不同的物理地址上。
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
当进程调用fork,控制转移到操作系统内核中的fork代码后,会进行下列操作:
fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
fork用法:
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
而当系统中进程太多时,或是用户进程数超过了限制,就会造成调用失败。
通常,父子代码共享(但是子进程是从fork之后才开始执行的),父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
当fork执行之后,子进程被创建出来,包括了进程控制块和虚拟内存存储区域的结构体以及页表,代码继承父进程。如果父子进程没有修改数据,那其页表的状态都会记录为只读,同时指向物理内存的相同地方,当二者需要修改数据时,就更改页表状态,同时修改页表映射关系,并在对应物理内存位置拷贝数据进行修改。
进程终止有三种场景:
对于前两者,可以通过echo $?指令来查看进程退出码(从main函数返回,调用exit和_exit的都表示进程退出,其他函数则不然)
#include
void _exit(int status);
void exit(int status);
//参数:status(进程退出码) 定义了进程的终止状态,父进程通过wait来获取该值
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。
不同之处在于,exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
#include
#include
//等待任意一个退出的子进程
pid_t wait(int*status);
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL.
#include
#include
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid: pid=-1,等待任一个子进程。与wait等效。 pid>0.等待其进程ID与pid相等的子进程。
status: WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options: 为0表示阻塞等待
对于这两者而言:
注意:这两者都是系统调用!
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
以下是六种替换函数:
#include `
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
代码如下:
#include
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
//结束时需要在结尾加上NULL
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
xecve("/bin/ps", argv, envp);
exit(0);
}