关于进程的几个问题
1 什么是进程?什么是程序?两者区别在哪里?
简单来说,程序与进程的区别在于程序是存储在磁盘中的静态文件,而进程是把程序放到内存中进行运行的操作,是动态的
2 在linux系统中怎么查看进程?
查看全部进程:ps -aux 查找关键字进程:ps -aux|grep xxx
3 什么是进程标识符?
每个进程都有一个非负整数的ID号(唯一)类似于进程的身份证
特殊进程id:
pid = 0:交换进程
pid = 1:init进程(用于程序初始化)孤儿进程的新父进程
4 什么是父进程?什么是子进程?
父子进程是相对的关系。进程A创建进程B,A是B的父进程,B是A的子进程
5 C程序的存储空间分配?
常用API
getpid()//获取当前进程
getppid()//获取父进程id
fork()//创建子进程
vfork()//创建子进程
需要包含头文件:
#include <sys/types.h>
#include <unistd.h>
函数原型:
pid_t getpid(void);
pid_t getppid(void);
编程演示
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
pid_t pid;
pid = getpid();
printf("current pro id:%d\n",pid);
fork();
if(pid == getpid()){
printf("this is father pro ,current pro id is %d\n",getpid());
}
else{
printf("this is child pro ,current pro id is %d\n",getpid());
}
return 0;
}
结果
从代码可以看出:程序刚开始运行时,属于该程序进程id号被打印出来是15629,当调用fork函数后,创建了一个子进程,把调用点以后的代码段复制了一份同时运行,结果也如图片可见父子进程的id均被打印出来了
fork与vfork函数创建子进程
所需包含头文件:
#include <sys/types.h>
#include <unistd.h>
作用:都是创建子进程,fork函数在创建子进程时会开辟另外的存储空间,而vfork函数不会开辟新的存储空间,直接使用父进程的存储空间
返回值:
调用成功函数返回两个数值,一次为0(子进程),一次为非负数(父进程)
调用失败返回-1
创建过程(利用早期linux机制来讲解)
早期linux创建新进程后采用全拷贝方式(将原进程的堆,栈,以及打开的文件以及IO流全部再拷贝一份到新进程中,会造成资源浪费,后期采用写时拷贝:即不对代码中数据进行修改时,父进程与子进程共享代码段,当需要对数据进行修改时把数据段进行拷贝)
vfork()创建子进程
返回值:与fork函数相同
与fork最主要区别:vfork不拷贝,直接使用父进程存储空间
vfork保证子进程先运行,直到子进程调用exit父进程才能继续运行
进程退出
正常退出:
Main函数调用return函数
进程调用exit()(标准C库)
进程调用_exit(),_Exit()(属于系统调用)
异常退出:
调用abort
进程收到某些信号时,例如ctrl+c
最后一个线程对取消请求做出响应
无论怎么退出,操作系统执行进程退出,关闭所有打开的描述符,释放使用的存储器
对于终止函数(exit(),_exit(),_Exit()),进程退出时把退出状态作为参数传递给函数(通常在父进程等待子进程退出收集执行状态时适用)
exit函数
void exit(int status);
需要包含头文件:
#include<stdlib.h>
_exit函数
void _exit(int status);
需要包含头文件:
#include<unistd.h>
_Exit函数
void _Exit(int status);
需要包含头文件:
#include<stdlib.h>
获取终止状态:wait与waitpid函数
父进程等待子进程退出并收集子进程的退出状态
分两种情况:
子进程正常退出
子进程异常退出
两种情况均会返回退出状态,正常情况下父进程均需要对子进程退出状态进行收集,如果不对子进程退出状态进行收集子进程就会变成僵尸进程
常用API
wait()
waitpid()
作用
如果有子进程还在运行,父进程堵塞
如果一个子进程已终止运行,正等待父进程获取终止状态,父进程取得子进程的终止状态立即返回
如果没有任何子进程,则立即出错返回
区别
从作用上:
wait单纯将父进程置阻塞状态,保证子进程先运行完。waitpid设置选项使得父进程不进入阻塞态(宏定义)
孤儿进程概念:父进程不等待子进程退出,在子进程结束前就结束了自己的生命
linux系统机制:为了避免孤儿进程过多,由init进程来收留孤儿进程,变成孤儿进程的父进程(编程实现)
wait函数
pid_t wait(int *wstatus);//status为整型数指针,不关心退出状态如何,只存储子进程退出状态
需要包含头文件:
#include <sys/types.h>
#include <sys/wait.h>
先来看如果父进程不收集子进程退出状态子进程的情况
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = getpid();
printf("current pro id:%d\n",pid);
pid = vfork();
if(pid > 0){
while(1){
printf("cnt:%d\n",cnt);
printf("this is father pro ,current pro id is %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child pro ,current pro id is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
从结果看,子进程运行三次后退出,父进程没有调用wait函数时,调用vfork函数创建的子进程在退出后变成了僵尸进程(Z+)而如果系统中存在过多僵尸进程会导致系统资源被占用进而引起崩溃,所以我们在子进程退出后一定要收集子进程退出状态
用法:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
pid_t pid;
int cnt = 0;
int status = 10;
pid = getpid();
printf("current pro id:%d\n",pid);
pid = fork();
if(pid > 0){
wait(&status);
printf("child quit,quit status = %d\n",WEXITSTATUS(status));
while(1){
printf("cnt:%d\n",cnt);
printf("this is father pro ,current pro id is %d\n",getpid());
sleep(1);
}
}
else if(pid == 0){
while(1){
printf("this is child pro ,current pro id is %d\n",getpid());
sleep(1);
cnt++;
if(cnt == 3){
exit(3);
}
}
}
return 0;
}
从代码和结果看,程序调用fork函数后,父进程调用wait函数等待子进程运行完退出并收集子进程退出状态,子进程退出后,操作系统把子进程运行过的所有痕迹删除,利用ps指令查看会发现没有关于子进程的信息,退出状态码也是人为设置的3,如果需要令退出状态码正确显示,便需要利用下图的宏对父进程收集到的子进程退出状态进行解析。
waitpid函数
pid_t waitpid(pid_t pid, int *wstatus, int options);
需要包含头文件:
#include <sys/types.h>
#include <sys/wait.h>
waitid函数
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
exec族函数(进程替换)
作用:在我们调用fork函数创建出子进程时,我们会在子进程里执行另一个程序。
可调用什么类型的文件:我们可以调用exec族函数在子进程里执行一个可执行文件,这个可执行文件可以是二进制文件,也可以是任何linux下可执行的脚本文件
需要包含的头文件
返回值:当exec函数执行成功时,没有返回值,当调用失败时,会设置errno并返回-1,并从原程序调用点继续往下执行
各函数区别(以execl,execlp,execv,execvp为例)
各函数原型
int execl(const char *path, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
所属头文件
#include <unistd.h>
execl:在给出绝对路径下寻找指定文件并添加参数
execlp:在环境变量中寻找指定文件
execv:事先把执行参数放到指定数组中,在给出的绝对路径寻找指定文件并根据数组中的元素对指定文件进行执行
execvp:事先把执行参数放到指定数组中,在环境变量中寻找指定文件并根据数组中的元素对指定文件进行执行
system函数:简单来说就是execl再封装一层
用法:在command指针处输入需要执行的指令
int system(const char *command);
返回值:执行成功时返回进程的状态值,当sh不能执行时,返回127,失败返回-1
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
system("ls");
return 0;
}
popen函数
函数原型
FILE *popen(const char *command, const char *type);
用法:跟system函数用法相近,最后加上对管道的操作(r/w)
与system函数比较:
相同点:与system一样都是execl二次封装
不同点:popen可以获取运行结果,popen函数简单来说就是在执行命令后把结果重定向到调用popen函数创建的管道中,此时结果为二进制流,可以调用文件标准C库的fread函数来把二进制流的数据读出来
举例
调用system函数时
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
char ret[1024] = {0};
system("ls");
printf("ret = %s\n",ret);
return 0;
}
使用system函数结果:只执行命令并不会保存到文件中
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
char ret[1024] = {0};
FILE *fp;
fp = popen("ls","r");
int nread = fread(ret,1,1024,fp);
printf("read %d bytes from stream\n",nread);
printf("ret = %s\n",ret);
return 0;
}
使用popen函数结果演示
就此,进程部分暂告一段落