目录
三 进程创建
五 exec函数族
六 进程控制
一 进程基本概念
程序包含:
二进制格式标识(包含描述可执行文件格式的元信息(大小等))
机器语言指令:对程序算法进行编码
程序入口地址
数据(程序的变量初始值,字面量值等)
符号表和重定位表:描述函数,变量的位置及名称,包含调试运行时的符号解析。
共享库和动态链接信息:他们的路径名
用来指导如何创建进程。 只占据磁盘,不占据cpu,内存等资源。
进程: 占据磁盘,占据cpu,内存等资源。
是正在运行的程序的实例,操作系统执行的基本单元。实质上是内核定义的抽象实体,并为该实体分配的用于执行程序的各系统资源。
从内核看,由用户内存空间和内存数据结构组成。内核数据结构用于维护进程状态信息,记录进程标识号,虚拟内存表,进程资源使用等信息。
时间片: 处理器分配给每个正在运行的进程的微观上一段CPU时间。通常很短(Linux:5-800ms)。 第一次给每个进程分配相同时间,轮番执行后,所有时间片耗尽,内核为其重新计算并分配。
并行:多个处理器上同时多条指令运行
并发:一个处理器上多条指令快速切换运行,导致看上去在同时运行。
PCB:进程控制块,由内核为每个进程分配,维护进程相关信息。(本质是一个结构体)
进程id: pid_t类型 0-32767 唯一但可以重用 进程状态:就绪,运行,挂起,停止等。
进程切换时需要保存,恢复的CPU寄存器 虚拟地址空间信息
控制终端信息 当前工作目录
文件描述符表等
除了init进程,任何进程都由另一个进程创建,对应进程号PPID 。
进程组是一个/多个进程的集合,可以接受同一终端的各种信号,有一个PGID。
pid_t getpid(void)//获取进程号
pid_t getppid(void)//获取父进程号
pid_t getpgid(pid_t pid)//获取pid所属组进程号
二 进程状态转换
进程状态:(新建),就绪,运行,阻塞,(终止)岁进程执行和外界条件变化而转换。
就绪态:进程具备运行条件,等待系统分配处理器才可以运行。处于就绪队列中
运行态: 拥有CPU正在运行
阻塞态:(wait/sleep态) 不具备运行条件,正等待其他事件完成。
新建态:进程刚被创建,还未进入就绪队列。
终止态:完成任务结束或异常终止或被操作系统,有终止权的进程所终止。终止后保留在操作系统里等待善后,当其他进程完成对该进程的信息抽取后,操作系统删除该进程 。
查看命令:
ps aux/ajx
u:进程详细信息
x:没有控制终端的信息
j:与作业相关的信息
top可以实时显示进程动态
显示后按 M 可以按内存大小排序, U 用户名可以显示所属该用户的进程
kill 杀死进程
kill 进程号可以杀死别的进程 kill -9(-SIGKILL) 进程号,强制杀死,可以用来杀死自己进程。(kill -9是发了一个信号,该信号能杀死进程)
ps:运行进程时 加上& 如 ./a.out &可以让他在后台运行,不阻塞终端,只是输出到终端
进程创建
int main()
{
//成功则在父进程中返回创建的子进程的pid_t. 在子进程中返回0.父进程返回-1则创建失败
//失败:1 当前进程数达到系统规定上线,errno:EAGAIN 2 系统内存不足,errno:ENOMEM
pid_t pid1 = fork();
cout<<pid1<<endl;
if(pid1>0)
{//父进程
printf("I am father pid %d %d\n",getpid(),getppid());
}
else if(pid1==0)
//子进程
printf("I am child pid%d %d\n",getpid(),getppid());
for(int i=0;i<5;i++)
{//父子进程交替执行
cout<<i<<" "<<getpid()<<endl;
sleep(1);
}
return 0;
}
父子进程虚拟地址空间
fork()时通过写时复制实现(可以推迟甚至避免拷贝)(针对物理空间)
刚开始内核让父子进程只读地共享同一个地址空间(用户区数据,fd表),直到需要写入时才会复制地址空间,让他们各自拥有自己的地址空间。子进程会拷贝父进程的用户区,内核区也会拷贝,但是pid不同。
父子进程相同的fd指向相同的文件,引用计数增加,
exec函数族
(execute) 作用:根据指定文件名找到可执行文件,即在调用程序内部执行一个可执行文件
执行成功后不会返回,原程序后面内容不再执行, 失败返回-1,从原程序调用点接着往下。
int execl(const char* path,const char*arg,...);
//path:需要执行的文件路径 推荐绝对路径
//args: 执行文件的名称,函数需要的参数列表...需要以null结束(哨兵)
int main()
{
pid_t pid=fork();
if(pid>0)
{
cout<<"i am parent process, pid: "<<getpid()<<endl;
}
else if(pid==0)
{
execl("test","test",nullptr);
//execl("/bin/ps","ps","aux",nullptr); //which ps可以查找其所在位置
cout<<"i am child\n";
}
cout<<"alllll, "<<getpid()<<endl;
return 0;
}
test.cpp
int main()
{
cout<<"i am test()\n";
return 0;
}
输出结果
int execlp(const char*file,const char* args,...)
//没指定路径时 从环境变量里查找
int execv(const char*file,char* const argvs[],...) //参数用数组存储
进程控制
退出进程 exit(int status)(stdlib.h) _exit(int status)(unistd.h)
exit会刷新IO缓冲,输出缓冲区里的内容,_exit()会直接退出,缓冲区里的内容被丢弃。
孤儿进程 :父进程结束,子进程还在运行。被init进程接管回收资源。所以孤儿进程不会有危害。
僵尸进程:进程终止时,会释放用户区数据,但是内核区PCB需要父进程释放,如果父进程不调用wait() /waitpid(),该进程号就会一直占用,PCB一直存在内核,变为僵尸进程。 僵尸进程不能被kill -9杀死。
解决方案:
1)此时可以杀死父进程,使init接管该僵尸进程,回收资源。
2) wait()/waitpid()得到子进程退出状态同时清除该进程的相关资源,但一次只能处理一个进程。
pid_t wait(int *wstatus)
//阻塞 直到一个子进程退出,或者收到一个不能被忽略的信号才被唤醒,往下执行
*wstatus: 进程退出时的状态信息,传入的是一个int类型的地址,传出参数。
成功返回被回收的进程id,失败:-1(所有子进程都结束)
int main()
{
pid_t pid;
for(int i=0;i<5;i++)
{
pid=fork();
if(pid==0)
break;//防止产生孙子进程
}
if(pid>0)
{
while(1)
{
cout<<"parent: "<<getpid()<<endl;
cout<<"destroy: "<<wait(nullptr)<<endl;
sleep(1);
}
}
else if(pid==0)
{
while(1)
{
cout<<"child: "<<getpid()<<endl;
sleep(1);
}
}
return 0;
}
可以看到wait被阻塞。
退出信息相关宏
int res;
wait(&res);
if(WIFEXITED(res))
cout<<WEXISTSTATUS(res)<<endl;
if(WIFSIGNALED(res)
cout<<WTERMSIG(res)<<endl;
pid_t wait_pid(pid_t pid,int * wstatus,int options) 可以设置不阻塞与指定等待哪个子进程。
//pid>0 回收某个子进程的pid =0: 回收当前进程组的所有子进程 =-1:回收所有子进程,相当wait().<-1: 回收某个进程组的组id的绝对值。
//options 0:阻塞 WNOHANG 非阻塞
//>0 返回子进程id =0 表示还有子进程没有退出 -1 都退出了