基本概念:
程序 - 主要指存储在磁盘上的文件
进程 - 主要指在内存中运行的程序
在windows系统中,我们常用调用任务管理器进行查看系统进程(Ctrl+Alt+Delete)
在unix/linux系统中,我们常用ps -[选项] 命令来查看系统进程
PID ---- 进程的编号
TTY ---- 终端的次要装置号码
TIME ---- 消耗CPU的时间
CMD ---- 具体的命令以及参数
ps -aux : 表示显示所有包括其他使用者的进程
ps -aux|more : 表示分屏显示所有的进程信息
USER ---- 属主信息(掌握)
PID ---- 进程号(重点)
%CPU ---- 占用CPU的百分比
%MEM ---- 占用内存的百分比
VSZ ---- 虚拟内存大小
RSS ---- 物理内存大小
TTY ---- 终端的次要装置号码
STAT ---- 进程的状态(重点)
START ---- 进程的启动时间
TIME ---- 消耗CPU的时间
COMMAND ---- 具体的进程名称以及选项(掌握)
进程的常见状态主要有:
- S ---- 休眠状态
- s ---- 进程的领导者(表示拥有子进程)
- Z ---- 僵尸进程(进程已结束,资源没有回收)
- R ---- 正在运行的进程
- O ---- 可运行的状态(就绪状态)
- T ---- 挂起状态
- < ---- 优先级高的进程
- N ---- 优先级低的进程
…
ps -ef : 表示使用全格式的方式显示进程信息
ps -ef|more : 表示分屏显示进程的信息
PID ---- 进程号
PPID ---- 父进程的进程号
kill -9 进程号 : 表示杀死指定的进程
进程管理:
目前主流的操作系统都支持多进程
如果进程A启动了进程B,那么进程A叫做进程B的父进程
,进程B叫做进程A的子进程
操作系统的多进程组成树型结构
,其中进程0是系统内部的进程
,负责启动进程1(init进程)
和进程2,其他所有的进程都是由进程1和进程2直接/间接
的启动起来
PID ---- 进程号:是进程在操作系统中的唯一标识
进程号的分配采用延迟重用的策略。
即:在每一个时刻都可以保证进程号唯一. (进程号是以递增的形式分配的,当进程号分配到头的时候,才从0开始分配未被占用的进程号)
如何获取当前进程的进程号?
- getpid() ----主要用于获取当前进程的进程号
( 返回值类型:pid_t => int 一般从0开始)
- getppid() ----主要用于获取当前进程父进程的进程号
( 返回值类型:pid_tpid_t => int 一般从0开始)
- getuid() -主要用于获取用户的ID
(返回值类型:uit_tuid_t => unsigned int)
- getgid() -主要用于获取用户所在用户组的ID
(返回值类型:gid_tuid_t => unsigned int)
示例代码:
//获取各种ID信息
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
printf("当前进程的进程号是:%d\n",getpid());
printf("当前进程的父进程是:%d\n",getppid());
printf("当前用户的编号是:%d\n",getuid());
printf("当前用户的用户组ID是:%d\n",getgid());
return 0;
}
运行结果:
进程的创建
1. fork函数
函数功能:
主要用于以复制正在运行进程的方式来创建新的进程,其中新进程叫做子进程,正在运行的进程叫做父进程
返回值:
函数调用成功时:父进程返回子进程的PID,子进程返回0
函数调用出错时:父进程返回-1,子进程没有被创建
示例代码:
//使用fork函数创建子进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
printf("main函数开始执行\n");
pid_t pid = fork();
if( pid == -1)
{
perror("fork"),exit(-1);
}
printf("main函数结束%d\n",pid);
return 0;
}
运行结果:
main函数开始执行
main函数结束4087 //由父进程打印
main函数结束0 //由子进程打印
2. fork创建子进程的工作方式
a.fork函数之前的代码,父进程执行一次
b.fork函数之后的代码,父子进程各自执行一次
c.fork函数的返回值,父子进程各自返回一次
对上述代码作修改,让父子进程各自执行不同的工作
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
printf("main函数开始执行\n");
//创建子进程
pid_t pid=fork();
//判断是否创建成功
if(-1 == pid)
{
perror("fork"),exit(-1);
}
//如果是子进程
if(0 == pid)
{
printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
}
else
{
printf("我是父进程%d,我的子进程是:%d\n",getpid(),pid);
}
return 0;
}
运行结果:
对上述代码再作修改(父进程先于子进程结束)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
int main(void)
{
printf("main函数开始执行\n");
//创建子进程
pid_t pid=fork();
//判断是否创建成功
if(-1==pid)
{
perror("fork"),exit(-1);
}
//printf("main函数结束%d\n",pid);
//如果是子进程
if(0 == pid)
{
printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
//睡眠1秒
sleep();
printf("我是子进程%d,我的父进程是:%d\n",getpid(),getppid());
}
else
{
printf("我是父进程%d,我的子进程是:%d\n",getpid(),pid);
}
return 0;
}
运行结果:
3. fork创建的父子进程之间的关系
a.父进程启动子进程,父子进程同时启动,如果子进程先结束,会给父进程发信号等,让父进程帮助子进程回收资源
b.如果父进程先结束,子进程变成孤儿进程,子进程会变更父进程(重新指定init进程为新的父进程),init进程也叫做孤儿院
c.如果子进程先结束,但是父进程由于各种原因并没有收到子进程发来的信号,也就是没有帮助子进程回收资源,那么子进程会变成僵尸进程
注意: fork()函数创建子进程之后,父子进程的先后执行次序是不确定的,由操作系统负责调度 (两个进程各自运行,互不干扰)先执行父进程还是先执行子进程是由系统决定的(我们是不确定的)
4.fork函数创建的父子进程之间的内存资源关系
进程的内存区域划分:代码区,全局区,堆区,栈区
示例:观察父子进程间的内存关系
//观察父子进程之间的内存资源关系
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
int var1 = 1; //全局变量 全局区
int main(void)
{
int var2=1; //局部变量 栈区
//pc指向堆区 pc本身在栈区
char* pc=(char*)malloc(10*sizeof(char));
strcpy(pc,"hello");
//创建子进程
pid_t pid=fork();
if(-1 == pid)
{
perror("fork"),exit(-1);
}
//子进程
if(0 == pid)
{
int var3=2;
var1=2;
var2=2;
pc[0]='H';
printf("子进程中:var1=%d,var2=%d,var3=%d,pc=%s\n",var1,var2,var3,pc);
exit(0); //终止子进程
}
sleep(1); //保证子进程早于父进程结束
printf("父进程中:var1=%d,var2=%d,pc=%s\n",var1,var2,pc);
return 0;
}
运行结果:
使用fork函数创建的子进程会复制父进程中除了代码区(代码区没有拷贝的必要)之外的其他内存区域,而代码区和父进程共享
5. 进程扩展
a.调用fork函数两次(进程数=2的fork()个数的次方)
fork();
fork();
一共4个进程:1个父进程+2个子进程+1个孙子进程
b.如何创建出3个进程呢?
fork();
if(0!=pid) //父进程才能执行
{
fork();
}
一共3个进程:1个父进程+2个子进程
c.俗称:"fork炸弹",会让电脑死机(早期的电脑病毒)
while(1)
{
fork();
}
6.进程的终止
- 正常终止进程的方式:
a.在main函数中执行了return 0;
b.调用exit()函数终止进程
c.调用_exit()/Exit()函数终止进程
d.最后一个线程返回
e.最后一个线程调用了pthread_exit()函数 - 非正常终止进程的方式:
a.采用信号终止进程,比如:ctrl+c
b.最后一个线程被其它线程调pthread_cancel()取消
6.1进程终止函数的比较
_exit()和_Exit()函数
#include<unistd.h>
void _exit(int status); // =>uc库的函数
函数功能:
主要用于引起正常进程的终止,并且将参数值(status)&0377(相当于取低八位)的结果返回给父进程 在终止进程的期间会调用由atexit()/on_exit()函数注册(准备)过的函数
#include<stdlib.h>
void _Exit(int status); // =>标c库的函数
函数功能: 主要用于立即终止正在运行的进程,参数status的值会被返回给父进程, 父进程调用wait系列函数进行获取,该参数作为终止进程的退出状态信息(也就是退出的原因)
exit()函数
#include<stdlib.h>
void exit(int status);
函数功能: 主要用于引起正常进程的终止,并且将参数值(status)&0377(相当于取低八位)的结果返回给父进程 在终止进程的期间会调用由atexit()/on_exit()函数注册(准备)过的函数
atexit()函数
#include<stdlib.h>
int atexit(void (*function)(void));//参数是函数指针,实参传函数的地址(也就是函数名)
函数功能:
主要用于对参数指定的函数进行注册/准备,而被该函数注册过的函数会在程序调用exit函数的期间被调用,或者在main函数执行return之后被调用
各种终止进程函数的调用示例代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//自定义注册函数
void show(void)
{
printf("我就是准备的注册函数\n");
}
int main(void)
{
//使用atexit函数进行注册
atexit(show);
printf("main函数开始执行\n");
_exit(100);//_exit() _Exit()不引起show函数的调用
printf("main函数结束\n");
return 0;
}
运行结果:
//各种终止进程函数的调用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//自定义注册函数
void show(void)
{
printf("我就是准备的注册函数\n");
}
int main(void)
{
//使用atexit函数进行注册
atexit(show);
printf("main函数开始执行\n");
exit(100); //exit()会引起show函数的调用
printf("main函数结束\n");
return 0;
}
运行结果;
//各种终止进程函数的调用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
//自定义注册函数
void show(void)
{
printf("我就是准备的注册函数\n");
}
int main(void)
{
//使用atexit函数进行注册
atexit(show);
printf("main函数开始执行\n");
printf("main函数结束\n");
return 0; return//引起show函数的调用
}
运行结果:
7.进程的等待
wait函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);
函数功能:
主要用于挂起当前正在运行的进程进入阻塞状态,直到有一个子·进程终止为止(进程的终止作为进程状态发生改变的一种情况) 如果参数不为空,那么wait函数会将获得的状态信息存储到status指向的int类型空间中 成功返回终止的子进程ID,失败返回-1
WIFEXITED(status) => 判断子进程是否正常终止.若子进程正常终止(子进程调用exit/_exit函数或从main函数中执行了return均为子进程的正常终止),则返回true
WEXITSTATUS(status) => 获取子进程的退出状态信息
//wait函数的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(void)
{
//1.创建子进程
pid_t pid=fork();
if(-1==pid)
{
perror("fork"),exit(-1);
}
//2.子进程启动,占用10秒后终止
if(0==pid)
{
printf("子进程%d开始运行\n",getpid());
sleep(10);
printf("子进程结束\n");
exit(100);
}
//3.父进程等待子进程结束,并且获取退出信息
int status=0;
int res=wait(&status);
if(-1==res)
{
perror("wait"),exit(-1);
}
printf("子进程总算结束了,status=%d,res=%d\n",status,res);
return 0;
}
运行结果:
注:status中只是包括了子进程的退出码100,除了100之外还包括了其它的一些信息,所以打印出25600.怎样把status中子进程终止时的退出码100给获取出来?即如何获取真正的退出码?=>使用宏WEXITSTATUS(status)
对上述程序作修改:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(void)
{
//1.创建子进程
pid_t pid=fork();
if(-1==pid)
{
perror("fork"),exit(-1);
}
//2.子进程启动,占用10秒后终止
if(0==pid)
{
printf("子进程%d开始运行\n",getpid());
sleep(10);
printf("子进程结束\n");
exit(100);
}
//3.父进程等待子进程结束,并且获取退出信息
int status=0;
int res=wait(&status);
if(-1==res)
{
perror("wait"),exit(-1);
}
printf("子进程总算结束了,status=%d,res=%d\n",status,res);
if(WIFEXITED(status));
{
printf("子进程正常终止,子进程的退出状态信息是:%d\n",WEXITSTATUS(status));
}
return 0;
}
运行结果:
waitpid函数
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int* status,int options);
函数功能:主要用于等待指定的子进程状态发生改变
第一个参数:进程号(指定要等待哪一个进程)
<-1:等待进程组ID为pid绝对值的任意一个子进程(了解)
-1:等待任意一个子进程(重点掌握)
0:等待和调用进程在同一个进程组的任意一个子进程(了解)
>0:等待进程号为pid的子进程(重点掌握)
第二个参数:指针变量,用于获取子进程的状态信息
第三个参数:等待的方式,默认给0即可
返回值:成功返回等到的子进程ID,失败返回-1
//waitpid函数的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(void)
{
//启动3个进程:1个父进程+2个子进程
pid_t pid1,pid2;
pid1=fork();
if(-1==pid1)
{
perror("fork"),exit(-1);
}
//父进程
if(0!=pid1)
{
//再次创建子进程
pid2=fork();
}
//子进程一
if(0==pid1)
{
printf("子进程一%d开始启动\n",getpid());
sleep(5);
printf("子进程一结束\n");
exit(100);
}
//子进程二
if(0==pid2)
{
printf("子进程二%d开始启动\n",getpid());
sleep(10);
printf("子进程二结束\n");
exit(200);
}
printf("父进程开始等待...\n");
int status=0;
//等待任意一个子进程,子进程一先结束
int res=waitpid(-1,&status,0);
if(-1==res)
{
perror("waitpid"),exit(-1);
}
printf("等到的子进程是:%d,得到的状态信息是:%d\n",res,WEXITSTATUS(status));
return 0;
}
运行结果:
对上述程序作修改:
//waitpid函数的使用
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(void)
{
//启动3个进程:1个父进程+2个子进程
pid_t pid1,pid2;
pid1=fork();
if(-1==pid1)
{
perror("fork"),exit(-1);
}
//父进程
if(0!=pid1)
{
//再次创建子进程
pid2=fork();
}
//子进程一
if(0==pid1)
{
printf("子进程一%d开始启动\n",getpid());
sleep(5);
printf("子进程一结束\n");
exit(100);
}
//子进程二
if(0==pid2)
{
printf("子进程二%d开始启动\n",getpid());
sleep(10);
printf("子进程二结束\n");
exit(200);
}
printf("父进程开始等待...\n");
int status=0;
//等待进程号为pid1的进程,子进程1
int res=waitpid(pid1,&status,0);
if(-1==res)
{
perror("waitpid"),exit(-1);
}
printf("等到的子进程是:%d,得到的状态信息是:%d\n",res,WEXITSTATUS(status));
return 0;
}
运行结果:
有错误的地方希望大家及时的指出来哦~
看完后觉的有所收获的小伙伴点个赞嘛~