目录
什么是进程
Linux下操作进程的相关命令
进程的状态(生老病死)
创建进程系统api介绍:
fork()
父进程和子进程的区别
vfork()
进程的状态补充:
孤儿进程
僵尸进程
回收进程资源api介绍:
wait()
waitpid()
exit()
popen
什么是进程
一个程序是由源代码在编译后产生的,格式为ELF的,存储于硬盘的文件,在Linux中,程序文件的格式都是ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。而这个动态的实体,程序中的代码和数据,被加载到内存中运行的过程,就叫进程,程序是静态的,进程是动态的
Linux下操作进程的相关命令
ps 查看当前终端运行的进程
ps -e 查看当前系统的所有进程
./ 程序名 运行当前目录下的程序
top 查看当前Linux系统的进程运行情况
pstree 查看进程树
killall 杀死所有进程 后面加名字即为杀死所有叫这个名字的进程
kill 杀死一个进程 后面跟进程的pid即为杀死指定的pid进程
killall/kill -STOP 进程名/进程PID #暂停进程
killall/kill -CONT 进程名/进程PID #继续运行继承
进程的状态(生老病死)
文字是从某本书摘抄的,我觉得写的非常好(当然图也是)
创建进程系统api介绍:
fork()
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
创建一个子进程
成功时返回两个返回值
返回子进程pid,返回子进程pid时,执行父进程
返回值0,返回值为0时,执行子进程
创建失败时返回一个值为-1
返回值-1时不创建子进程
pid_t pid; //pid_t跟int含义相同
if(pid == 0)
{
//子进程的代码
}
if(pid > 0)
{
//父进程的代码
}
父进程和子进程的区别
父子进程的以下属性在创建之初完全一样:
A) 实际UID和GID,以及有效UID和GID。
B) 所有环境变量。
C) 进程组ID和会话ID。
D) 当前工作路径。
E) 打开的文件。
F) 信号响应函数。
G) 整个内存空间,包括栈、堆、数据段、代码段、标准IO的缓冲区等等。
而以下属性,父子进程是不一样的:
A) 进程号PID。PID是身份证号码,哪怕亲如父子,也要区分开。
B) 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
C) 挂起的信号。这是所谓“悬而未决”的信号,等待着进程的响应,子进程不会继承这些信号。
子进程只会执行fork()语句下面的程序段,不会执行fork上的程序段
引用:”的确子进程包含有和父进程一样的代码和数据(虽然一样但的确是自己的一份)。 但别忘了,子进程复制的不仅是父进程的代码和数据,还包括状态,这个状态就包含有PC指针寄存器的值。 也就是说子进程创建完成后,他和父进程一样,PC指针都指向下一条语句, 因此子进程是从自身创建完成后的地方继续运行 ,而父进程运行过得代码将不再运行。“
参考文献:为什么fork创建子进程后,父进程中运行过的代码在子进程中不再运行了_fokk一个子进程后,父进程变量的值在子进程中仍然存在对吗-CSDN博客
分析下面的demo,判断printf打印了几次
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
fork();
fork();
fork();
printf("15678\n");
}
printf共打印8次,可以用一个阻塞函数阻塞程序运行,然后在Linux终端输入pstree查看原因
vfork()
创建一个子进程并阻塞父进程
fork()和vfork()的区别
1.资源复制方式:
fork:创建子进程时,子进程会复制父进程的数据段、堆栈段和代码段。虽然现代Linux通过写时复制(Copy-on-Write, COW)技术优化了这一过程,但本质上,父子进程拥有独立的地址空间。
vfork:子进程与父进程共享数据段。vfork创建的子进程基本上是一个轻量级进程,它不会复制父进程的地址空间,而是直接共享。
2.执行顺序:
fork:父子进程的执行顺序是不确定的。操作系统可能会先调度父进程或子进程运行。
vfork:保证子进程先运行。在子进程调用exec或exit之前,父进程不会运行。这确保了在子进程执行完毕或替换为新程序之前,父进程不会继续执行。
1.vfrok 保证子进程先执行,父进程后执行
2.fork 父子进程的内存独立,vfrok 父子进程共享数据段 (常量,静态数据,全局变量)。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
// 全局变量,存储数据段
int value = 0;
int main()
{
// 1.创建一个子进程
pid_t pid = vfork(); // 共享数据段
//pid_t pid = fork(); // 父子进程的内存完全独立
if (pid == 0) // 子进程
{
while (1)
{
printf("%d 我是子进程pid是%d 我的爸爸是%d\n", value, getpid(), getppid());
value++;
sleep(1);
if (value == 10)
{
printf("子进程结束\n");
exit(0); // 子进程结束
}
}
}
//sleep(12);
printf("父进程运行\n"); // 父进程被阻塞了,直到子进程结束
if (pid > 0) // 父进程
{
while (1)
{
printf("%d 我是父亲进程pid是%d 我的儿子是%d\n", value, getpid(), pid);
value--;
sleep(1);
}
}
}
可以看到在执行过程中父进程中的value的值受子进程的影响
进程的状态补充:
孤儿进程
如果一个子进程的父进程比子进程先死亡,那么这个子进程就会变成孤儿进程,由系统进程(init进程)进行管理
要防止孤儿进程的产生,父进程必须比子进程后死亡
僵尸进程
子进程比父进程先死亡,并且父进程未回收子进程的资源。那么子进程就会变成僵尸进程。
创建僵尸进程
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
for (int i = 0; i < 10; i++)
{
int pid = fork(); // fork 后父子进程谁先执行是不确定的,由操作系统调度决定
if (pid == 0)
{
printf("我是子进程:%d ,我的父亲是:%d ,子进程死亡\n", getpid(), getppid());
return 0; // 结束子进程
}
}
printf("父亲继续运行,等待任意键结束\n");
getchar(); // 挂起态
printf("父进程结束\n");
return 0;
}
使用ps -e查看僵尸进程
回收进程资源api介绍:
wait()
回收进程资源
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
成功时返回回收资源的进程的pid号
失败时返回-1
wait一次回收一次任一资源(回收的资源是随机的)
可以通过wait获取进程的退出状态
有在wait(int *status)里放一个整形指针,如果 status 不是一个空指针,则终止进程的终止状态就存放在它所指向的单元内,如果不关心终止状态,则可将该参数指定为空指针,如wait(NULL)
死亡原因由以下宏定义获取
因为wait执行一次只回收一次资源,所以想把所有子进程资源都回收需要不断循环
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
// 1.创建一个子进程
pid_t pid = fork();
if (pid == 0) // 子进程
{
int i = 0;
while (1)
{
printf("子进程执行中 %d\n", i++);
if (i == 20)
{
printf("子进程结束\n");
// 退出子进程
return 5;
}
sleep(1);
}
}
while (1)
{
ret = wait(NULL);
if (ret == -1) {
if (errno == EINTR) { // 返回值为-1的时候有两种情况,一种是没有子进程了,还有一种是被中断了
continue; //如果是被中断了就continue继续执行
}
break;
}
}
printf("当前子进程资源已全部回收"\n);
printf("父进程为 %d\n", getpid());
}
通过wait获取退出状态
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
// 1.创建一个子进程
pid_t pid = fork();
if (pid == 0) // 子进程
{
int i = 0;
while (1)
{
printf("子进程执行中 %d\n", i++);
if (i == 20)
{
printf("子进程结束\n");
// 退出子进程,设置子进程退出状态
return 5;
}
sleep(1);
}
}
if (pid > 0) // 父进程
{
int status;
printf("等待子进程结束\n");
wait(&status);
// 获取子进程的退出状态 (死亡原因)
if (WIFEXITED(status)) // 判断子进程是否正常退出
{
printf("子进程正常退出\n");
printf("退出状态码: %d\n", WEXITSTATUS(status)); // 获取子进程的return 值
}
// 获取是否被信号杀死
if (WIFSIGNALED(status))
{
printf("子进程被信号杀死\n");
printf("杀死子进程的信号: %d\n", WTERMSIG(status)); // 获取杀死子进程的信号
}
}
}
waitpid()
pid_t waitpid(pid_t pid, int *wstatus, int options); 等待指定的子进程结束
pid:等待的进程pid
wstatus:进程的退出状态
options:等待属性设置阻塞与非阻塞 ,默认为 0 阻塞
exit()
void _exit(int status);
void exit(int status);
status 子进程的退出值
没有返回值
如果子进程正常退出,则 status 一般为 0。
如果子进程异常退出,则 status 一般为非 0
exit()退出时,会自动冲洗(fush)标准IO总残留的数据到内核,如果进程注册了“退出处理函数”还会自动执行这些函数。
而_exit()会直接退出。
popen
加载一个进程,并创建一个通信管道 (文件流指针)
FILE *popen(const char *command, const char *type);
command:需要加载的程序
type:加载的权限
"r" :读取
"w" :写入
"e" :可执行
返回值: 成功 文件流指针
失败 NULL
int pclose(FILE *stream); //关闭文件流指针
通过popen输入Linux命令行命令
#include <stdio.h>
int main()
{
// 1.加载 ls -l 命令(程序)
FILE *pf = popen("ls -l", "r");
if (pf == NULL){
printf("popen error\n");
return -1;
}
else{
printf("加载成功\n");
}
// 读取 ls -l 程序加载后的输出结果
while (1)
{
char buf[1024] = {0};
char *ret = fgets(buf, 1024, pf);
if (ret == NULL)
{
break;
}
printf("%s\n", buf);
}
// 关闭程序
pclose(pf);
}