目录
一、进程
1.1 进程的定义
进程是指在程序编译之后(可执行文件),运行起来的二进制文件。整个过程包含:进程的创建、运行、消亡。进程和程序之间的区别是进程属于动态过程,程序属性静态过程。
1.2 PCB(进程控制块)
为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。是一个结构体。
该结构体主要包含:1)进程id:每个进程都有唯一的id,用于区分各进程;进程号为“0”的进程是调度进程,常被称为交换进程(空闲进程),它不属于任何程序,是内核中的一部分。因此也被称为系统进程(不可见)。进程号为“1”的进程为始祖进程(祖先进程、init进程),系统内核启动后运行的第一个进程(系统初始化)。“init进程”负责引导系统、启动守护(后台)进程并且运行必要的程序。
2)进程状态:就绪态、执行态、等待态、表示进程当下的状态;
3)进程切换时需要保存和恢复的一些CPU寄存器状态;
4)描述虚拟地址空间的信息:虚拟地址和物理地址的对应关系;
5)当前工作目录;
6)umask掩码:保护文件创建和修改的权限;
7)文件描述符;
8)和信号相关的信息;
9)用户id和组id;
10)会话和进程组;
1.3 进程调度算法
1)先来先服务调度算法 2)短进程优先调度算法 3)高优先级调度算法 4)时间片轮询法。
1.4 进程状态
进程的最基本状态分为:就绪态、等待态(阻塞态、挂起态)以及执行态(运行态)。
1)执行态:进程占有CPU资源,正在运行。
2)就绪态:进程本身具备运行条件,系统分配处理器即可运行。
3)等待态:进程本身不具备运行条件,即使给它分配处理器也无法执行,进程正在等待某一事件的发生。
1.5 进程体系
1) 创建进程的进程为父进程,而新进程被称为子进程。
2) 每个进程都是由其它进程创建(除了init进程),每个子进程都有一个父进程。
3) 每个进程都属于某个用户和某个组。
1.6 进程命令
1)静态查询:ps 参数。-aux:查看进程状态,-axjf:查看进程详细信息
2)动态查询:top 参数
3)前后台进程应用
进程前后台切换:
① 在终端后台执行进程:./可执行文件 &
② 查看终端的后台进程:jobs
③ 把终端后台进程带入前台进程:fg 后台进程号
④ 把前台进程已停止状态进入后台进程:ctrl + z
杀死进程:kill 参数 进程ID
二、进程相关API函数
2.1 获取当前进程ID:getpid函数
头文件: #include<unistd.h>
函数原型: pid_t getpid(void);
函数功能说明: getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题。
返回值: 目前进程的进程识别码。(必然有返回)
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t currentpid = getpid();
printf("当前进程ID:%d\n",currentpid);
while(1);
return 0;
}
2.2 获取当前进程父进程ID:getppid
头文件: #include<unistd.h>
函数原型: pid_t getppid(void);
函数功能: getppid()用来取得目前进程的父进程识别码。
返回值: 目前进程的父进程识别码。(必然成功)
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t currentpid = getpid();
printf("当前进程ID:%d\n",currentpid);
pid_t currentppid = getppid();
printf("当前进程父进程ID:%d\n",currentppid);
while(1);
return 0;
}
2.3 创建进程
1)fork函数
头文件: #include<unistd.h>
函数原型: pid_t fork(void);
函数功能: ① 会产生一个新的子进程
② 其子进程会复制父进程的数据与堆栈空间;
③ 继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。
④ 子进程对变量的修改和父进程并不会同步。
返回值: 如果 fork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回 0。如果 fork 失败则直接返回-1,失败原因存于 errno中。
#include <stdio.h>
#include <unistd.h>
int main()
{
int A =10;
pid_t currentpid = getpid();
printf("当前进程ID:%d\n",currentpid);
//创建进程
pid_t pid=fork();
if(pid > 0)
{
while(1)
{
printf("父进程:%d\n",getpid());
sleep(1);
printf("父进程中A:%d\n",A);
}
}
else if(pid == 0)
{
while(1)
{
printf("子进程:%d---%d\n",getpid(),getppid());
sleep(1);
printf("子进程进程中A:%d\n",A++);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
2)vfork函数
头文件: #include<unistd.h>
函数原型: pid_t vfork(void);
函数功能: ①会产生一个新的子进程
②其子进程会复制父进程的数据与堆栈空间;
③继承父进程的用户代码,组代码,环境变量、已打开的文件代码、工作目录和资源限制等。
④Vfork函数数据区是父子进程共享的。(全局变量区堆区)
⑤父进程运行到vfork后会进入无法中断休眠态,子进程先运行,子进程结束后父进程才运行;
⑥子进程先于父进程退出,子进程变为僵尸进程;
⑦Exit退出函数,在子进程中调用exit函数会导致子进程退出;
⑧Execl该函数也会导致父进程不再阻塞,父子进程抢占运行;
返回值: 如果 vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回 0。如果 fork 失败则直接返回-1,失败原因存于 errno中。
#include <stdio.h>
#include <unistd.h>
int A =10;
int main()
{
pid_t currentpid = getpid();
printf("当前进程ID:%d\n",currentpid);
//创建进程
pid_t pid=vfork();
if(pid > 0)
{
while(1)
{
printf("父进程:%d\n",getpid());
sleep(1);
printf("父进程中A:%d\n",A);
}
}
else if(pid == 0)
{
for(int i = 0; i <15;i++)
{
printf("子进程:%d---%d\n",getpid(),getppid());
sleep(1);
printf("子进程进程中A:%d\n",A++);
}
}
else
{
perror("fork");
return 1;
}
return 0;
}
3)system函数(应用层)
头文件: #include<stdlib.h>
函数名称: int system(const char * string);
函数功能: system()会调用 fork()产生子进程,由子进程来调用/bin/sh-c string来执行参数 string 字符串所代表的命令,此命令执行完后随即返回原调用的进程。
返回值: 如果 system()在调用/bin/sh 时失败则返回 127,其他失败原因返回-1。若参数 string 为空指针(NULL),则返回非零值。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("test1可执行文件\n");
system("./fun"); //调用命令或需要运行的可执行文件
while(1)
{
printf("test1循环\n");
sleep(1);
}
return 0;
}
4)execl函数
头文件: #include<unistd.h>
函数原型: int execl(const char * path,const char * arg,....);
函数功能: execl()用来执行参数 path 字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的 argv〔0〕、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
返回值: 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于 errno 中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("test1可执行文件\n");
sleep(12);
execl("./fun","./fun",NULL);
while(1)
{
printf("test1循环\n");
sleep(1);
}
return 0;
}
2.4 进程退出
1)return函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("hello\n");
return 0;
}
2)exit函数
头文件: #include<stdlib.h>
函数原型: void exit(int status);
函数功能: exit()用来正常终结目前进程的执行,并把参数 status 返回给父进程,而进程所有的缓冲区数据会自动写回并关闭未关闭的文件。
返回值:无
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void fun()
{
printf("join in fun function\n");
exit(3);
printf("exit fun function\n");
}
int main()
{
printf("hello\n");
fun();
printf("main return \n");
return 0;
}
3)_exit函数
头文件: #include<unistd.h>
函数原型: void _exit(int status);
函数功能: _exit()用来立刻结束目前进程的执行,并把参数 status 返回给父进程,并关闭未关闭的文件。此函数调用后不会返回,并且会传递SIGCHLD 信号给父进程,父进程可以由 wait 函数取得子进程结束状态。
返回值:无
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void fun()
{
printf("join in fun function");
_exit(3);
printf("exit fun function\n");
}
int main()
{
printf("hello\n");
fun();
printf("main return \n");
return 0;
}
注意:exit与_exit都是结束进程,_exit是直接结束程序(不再运行_exit往下的内容),exit是刷新缓冲区再退出整个程序
2.5 注册退出函数(设置程序正常结束前调用的函数)
头文件: #include<stdlib.h>
函数原型:int atexit (void (*function)(void));
函数功能: atexit()用来设置一个程序正常结束前调用的函数。当程序通过调用 exit()或从 main 中返回时,参数 function 所指定的函数会先被调用,然后才真正由 exit()结束程序。
返回值: 如果执行成功则返回 0,否则返回-1,失败原因存于 errno 中。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void exitfunction(void)
{
printf("join in fun function\n");
printf("exit fun function\n");
}
int main()
{
atexit(exitfunction);
printf("hello\n");
exit(2);
printf("main return \n");
return 0;
}
2.6 释放无用子进程所占用资源
1)wait函数
头文件: #include <sys/types.h>
#include <sys/wait.h>
函数原型:pid_t wait (int * status)
函数功能: wait()会暂时停止目前进程的执行,直到有信号来到或子进程结 束。如果在调用 wait()时子进程已经结束,则 wait()会立即返 回子进程结束状态值。子进程的结束状态值会由参数 status 返回, 而子进程的进程识别码也会一快返回。如果不在意结束状态值,则 参数status 可以设成 NULL。
返回值: 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回 -1。失败原因存于 errno 中。
#include<sys/types.h>
#include<sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t pid = fork();
if(pid > 0)
{
while(1)
{
printf("我是父进程\n");
sleep(1);
wait(NULL);
}
printf("父进程退出\n");
}
else if(pid == 0)
{
for(int i = 0; i< 10; i++)
{
printf("我是子进程\n");
sleep(1);
}
}
return 0;
}
2)waitpid函数
头文件: #include <sys/types.h>
#include <sys/wait.h>
函数原型:pid_t waitpid(pid_t pid,int * status,int options);
函数功能: waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用 wait()时子进程已经结束,则 wait()会立即 返回子进程结束状态值。子进程的结束状态值会由参数 status 返回, 而子进程的进程识别码也会一快返回。如果不在意结束状态值,则 参数 status 可以设成 NULL。
参数 pid 为欲等待的子进程识别码, 其他数值意义如下:
pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
pid=-1 等待任何子进程,相当于 wait()返回值 范例 参考 wait()。
pid=0 等待进程组识别码与目前进程相同的任何子进程。
pid>0 等待任何子进程识别码为 pid 的子进程。
参数 option 可以为 0 或下面的 OR 组合:
WNOHANG 如果没有任何已经结束的子进程则马上返回,不予以 等待。
WUNTRACED 如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会。
子进程的结束状态返回后存于 status,底下有几个宏可判别结束情 况:
WIFEXITED(status)如果子进程正常结束则为非 0 值。
WEXITSTATUS(status)取得子进程 exit()返回的结束代码,一 般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为 真
WTERMSIG(status) 取得子进程因信号而中止的信号代码,一般 会先用 WIFSIGNALED 来判断后才使用此宏。
WIFSTOPPED(status) 如果子进程处于暂停执行情况则此宏值为 真。一般只有使用 WUNTRACED 时才会有此情况。
WSTOPSIG(status) 取得引发子进程暂停的信号代码,一般会先 用 WIFSTOPPED 来判断后才使用此宏。
返回值: 如果执行成功则返回子进程识别码(PID),如果有错误发生则返回 -1。失败原因存于 errno 中。
#include<sys/types.h>
#include<sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int status;
pid_t pid = fork();
if(pid > 0)
{
while(1)
{
printf("我是父进程\n");
sleep(1);
waitpid(0,&status,WNOHANG);
}
printf("父进程退出\n");
}
else if(pid == 0)
{
for(int i = 0; i< 10; i++)
{
printf("我是子进程\n");
sleep(1);
}
}
return 0;
}
三、特殊进程
3.1 孤儿进程
孤儿进程的本质上还是个普通的进程,是指一个父进程退出,而它的一个或者多个子进程还在运行,那么这些子进程就称为孤儿进程。孤儿进程会被系统指定的进程所收养,并由收养孤儿进程的进程对他们进程完成状态收集工作。
注:在“红帽Linux系统”中,孤儿进程将被“init进程(进程号为1)”的进程所收养,但是在“乌班图Linux系统中”,是由“upstart进程”所收养。
3.2 僵尸进程
Linux的进程在结束的时候不会对自己的资源进行清零,只有父进程结束时才会对子进程的资源进程清零。僵尸进程是指子进程先于父进程退出,父进程退出的时候没有回收子进程退出状态的资源(task_struct),因此子进程就称为僵尸进程(孩子死了,父亲没有收尸)。写程序时要尽量避免出现僵尸进程(系统垃圾),利用等待进程退出函数(wait/waitpid)可以解决僵尸进程问题。