Linux进程基础知识 linux常用进程管理命令
liebian365 2024-10-17 14:05 23 浏览 0 评论
程序:死的,只占用磁盘空间
进程:活的,运行起来的程序,占用系统资源(CPU,内存等)
单道程序设计:所有进程一个一个执行,A执行完了才能执行B
多道程序设计:进程相互穿插执行,并行执行
虚拟内存与物理内存映射
MMU:内存管理单元,完成虚拟地址到物理地址映射
不同进程用户空间内存映射到不同物理内存区域,而内核空间内存映射到同一块物理内存区域,因为操作系统就一个,在这个物理内存区域包括了不同进程的PCB(结构体)。
在用户空间分配一个数组(虚拟内存空间中数组地址是连续的),如果数组长度很长,在映射到物理内存上时,其在物理内存上的地址其实是不连续的,但不影响,反正我们平常取得地址都是虚拟内存地址。
MMU还可以修改程序访问内存级别,普通程序访问级别为0,操作系统为3,执行系统调用时,MMU修改了程序访问内存的访问级别,这个速度比较慢。
PCB进程控制块
进程状态
进程挂起的时候会释放CPU,例如sleep函数会挂起进程。
常用的几个环境变量
PATH:可执行文件搜索路径
SHELL:当前使用shell类型
TERM:当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
LANG:语言和字符编码方式
HOME:家目录路径
fork()
pid_t fork(void);
pid_t getpid(void);//获取进程号
pid_t getppid(void);//获取父进程号
uid_t getuid(void);//returns the real user ID of the calling process.
uid_t geteuid(void);//returns the effective user ID of the calling process.
gid_t getgid(void);//returns the real group ID of the calling process.
gid_t getegid(void);//returns the effective group ID of the calling process.
成功:父进程返回子进程进程号pid,子进程返回0;
失败:父进程返回-1,设置errno,不创建子进程;
子进程把父进程的数据复制一份
fork一般判断方式:
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
}
else if(!pid)
{
//...
}
else
{
//...
}
循环创建多个子进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/*
int main(int argc, char const *argv[])
{
for(int i=0;i<5;i++)
{
if(fork()==0)
{
printf("--I am the %d child process.\n", i+1);
break;
}
}
return 0;
}
*/
int main(int argc, char const *argv[])
{
int i = 0;
for(i=0;i<5;i++)
{
if(fork()==0)
{
//printf("--I am the %d child process.\n", i+1);
break;
}
sleep(1);//让后面打打印依序打印
}
if(i==5)
{
printf("--I am the parent process.\n");
}
else
{
printf("--I am the %d child process.\n", i+1);
}
return 0;
}
父子进程共享哪些内容?
子进程把父进程的数据复制一份,父子进程间遵循读时共享写时复制原则。
fork之后先执行父进程还是子进程是不确定的,取决于内核的调度算法。
gdb调试
使用gdb调试时,gdb只能跟踪一个进程,可以在fork函数调用之前,通过指令设置gdb调试跟踪父进程还是子进程,默认跟踪父进程。
set follow-fork-mode child:设置gdb在fork之后跟踪子进程
se follow-fork-mode parent:设置gdb在fork之后跟踪父进程
注意:一定要在fork函数之前设置才有效。
不管跟踪父进程还是子进程,程序运行结果都是一样的。只是单步调试的时候,走的语句不一样。
exec函数族
fork创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种exec函数去执行另一个程序,当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序开始处开始执行,调用exec并不创建新进程,exec前后进程的id不变。
将当前进程的.text,.data替换成要加载的程序的.text,.data。
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[]);
int execve(const char *filename, char *const argv[], char *const envp[]);
l:表示参数以列表方式提供
v:表示参数以数组方式提供
p:表示在环境变量path路径下查找可执行文件。
e:使用环境变量数组,不实用进程原有的环境变量,设置新加载程序的环境变量
事实上,只有execve是真正的系统调用,其他的几个函数最终都是调用它,execve在man第二节,其他在第三节。
参数:
path:完整可执行程序路径+文件名
file:在环境变量path路径下查找可执行程序文件,只指定文件名即可
arg:传递给新进程的参数,必须以NULL结尾,其中arg[0]一般存程序名,
返回值:通常情况下,exec函数不会返回,调用成功,跳转到新的程序入口处;错误时,返回-1,并设置errno。
execlp:通常用来调用系统程序,如ls、data、cp、cat等。其实,execlp也会在当前目录下查找可执行程序。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
execlp("ls","ls","-l","-a","-h",NULL);
//execlp("ls","ls","-alh",NULL);//这里这样写效果一样
perror("execlp error");//execlp出错才返回执行
exit(1);
}
else
{
sleep(1);
printf("--parent process---\n");
}
return 0;
}
execl:一般用于执行自己的程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
execl("./printf_hello","./printf_hello",NULL);//main函数命令行参数
//execlp("./printf_hello","./printf_hello",NULL);//这里也可以execlp,它也会查找当前路径
perror("execl error");//execl出错才返回执行
exit(1);
}
else
{
sleep(1);
printf("--parent process---\n");
}
return 0;
}
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("hello world\n");
return 0;
}
execvp:
char *arg[] = {"ls","-a","-l","-h",NULL};
execvp("ls",arg);
孤儿进程:
父进程先于子进程结束,子进程变为孤儿进程,systemd进程会成为孤儿进程的父进程
ps ajx
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
while(1)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(1);
}
}
else
{
printf("--parent processs--pid=%d--\n",getpid());
sleep(9);
}
return 0;
}
kill -9 杀掉子进程
僵尸进程:
进程终止,父进程尚未回收。子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程是不能用kill命令清除掉的,因为kill命令是用来终止进程的,而僵尸进程已经终止,可以kill掉僵尸进程父进程,来让系统进行回收。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(9);
}
else
{
while(1)
{
printf("--parent processs--pid=%d--\n",getpid());
sleep(1);
}
}
return 0;
}
子进程结束前:
子进程结束后:
defunct:死者
wait函数
阻塞回收任意一个子进程
一个进程在终止时会关闭掉所有的文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息;如果是正常终止,则保存着退出状态,如果是异常终止,则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或者waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在shell中用特殊变量$?查看,是因为shell是它的父进程,当它终止时,shell调用wait或者waitpid得到它的退出状态,同时彻底清除掉这个进程。
父进程调用wait回收子进程终止信息,wait函数包括三个功能:
1. 阻塞等待子进程退出
2. 回收子进程残留资源
3. 获取子进程结束状态(退出原因,正常退出->退出值,异常终止->终止信号)
pid_t wait(int *stat_loc);
stat_loc:传出参数,保存子进程的退出状态
成功:返回回收的子进程的pid,
失败:返回-1
判断进程退出状态的几个宏:
WIFEXITED(stat_val)//判断进程是否正常终止
WEXITSTATUS(stat_val)//取退出值
WIFSIGNALED(stat_val)//判断进行是否被信号终止
WTERMSIG(stat_val)//取信号值
WIFSTOPPED(stat_val)
WSTOPSIG(stat_val)
WIFCONTINUED(stat_val)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid;
pid_t wpid;
int status;
pid = fork();
if(pid==-1)
{
perror("fork error");
exit(1);
}
else if(!pid)
{
printf("--child process--pid=%d--ppid=%d--\n",getpid(),getppid());
sleep(10);
return 100;//查看退出状态是否为100
}
else
{
int wpid = wait(&status);
if(wpid==-1)
{
perror("wait error");
exit(1);
}
printf("--wpid=%d----\n",wpid);
if(WIFEXITED(status))//为真,说明子进程正常终止
{
printf("--child process exit with %d\n--",WEXITSTATUS(status));
}
if(WIFSIGNALED(status))//为真,说明子进程是被信号终止
{
printf("--child process killed with %d\n--",WTERMSIG(status));
}
}
return 0;
}
正常退出:
使用信号杀死:
当父进程不关心子进程的退出状态时,wpid=wait(NULL);
waitpid函数
pid_t waitpid(pid_t pid, int *stat_loc, int options);
返回值:
>0:表示成功回收的子进程pid
0:函数调用时,options设置为WNOHANG,并且,没有子进程结束。WNOHANG--wait no hang。hang:挂起
-1:失败,设置errno。
参数:
options:设置为WNOHANG时,指定回收方式为非阻塞。默认为0,阻塞。
WNOHANG return immediately if no child has exited.
pid:
>0:回收指定id的子进程
-1:回收任意子进程(相当于wait)
0:回收和当前调用watipid在一个组的所有子进程
<-1:回收指定进程组内的任意子进程
stat_loc:子进程退出状态,和wait函数中参数含义一样。
waitpid(-1,NULL,0);等价于wait(NULL);
waitpid示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int i = 0;
int pid,pid_waitfor,wpid;
for(i=0;i<5;i++)
{
pid = fork();
if(!pid)
{
break;
}
if(i==2)
{
pid_waitfor = pid;//指定回收第三个子进程,父进程中保存第三个子进程pid
}
//sleep(1);
}
if(i==5)
{
wpid = waitpid(pid_waitfor,NULL,0);//阻塞等待回收第三个子进程
//wpid = waitpid(-1,NULL,WNOHANG);//不阻塞回收任意一个子进程,没有结束的子进程,返回值为0。
//wpid = waitpid(-1,NULL,0);//阻塞回收任意一个子进程。
if(wpid==-1)
{
perror("wait error");
exit(1);
}
printf("--parent process--waitfor %d--\n",wpid);
}
else
{
sleep(1);
printf("--child process--pid=%d--\n", getpid());
}
return 0;
}
wait、waitpid函数一次只能回收一个子进程,要回收多个子进行需要循环。
循环回收多个子进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int i = 0;
int pid,wpid;
for(i=0;i<5;i++)
{
pid = fork();
if(!pid)
{
break;
}
}
if(i==5)
{
/*
while((wpid=waitpid(-1,NULL,0))!=-1)//注意最后返回-1
{
printf("--wait child--%d\n",wpid);
sleep(1);
}
*/
while((wpid=waitpid(-1,NULL,WNOHANG))!=-1)
{
if(wpid>0)
{
printf("--wait child--%d\n",wpid);
}
else if(wpid==0)
{
sleep(1);
}
}
}
else
{
sleep(1);
printf("--child process--pid=%d--\n", getpid());
}
return 0;
}
发现waitpid没有子进程可回收后返回-1。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int wpid;
//wpid=waitpid(-1,NULL,0);
wpid=waitpid(-1,NULL,WNOHANG); //两种结果一样,不管有没有设置阻塞,没有子进程可回收后返回-1
if(wpid==-1)
{
printf("没有子进程,返回-1\n");
}
if(wpid==0)
{
printf("没有子进程,返回0\n");
}
return 0;
}
相关推荐
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
-
明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
-
首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
-
齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...
- Qt界面——搭配QCustomPlot(qt platform)
-
这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
-
老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
-
用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
-
前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...
- 掌握Visual Studio项目配置【基础篇】
-
1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
-
随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...
- Visual Studio Community 2022(VS2022)安装图文方法
-
直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...
- Qt添加MSVC构建套件的方法(qt添加c++11)
-
前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...
- Qt为什么站稳c++GUI的top1(qt c)
-
为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...
- qt开发IDE应该选择VS还是qt creator
-
如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...
- Qt 5.14.2超详细安装教程,不会来打我
-
Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...
- Cygwin配置与使用(四)——VI字体和颜色的配置
-
简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- “版本末期”了?下周平衡补丁!国服最强5套牌!上分首选
- VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符"
- 东营交警实名曝光一批酒驾人员名单 88人受处罚
- Qt界面——搭配QCustomPlot(qt platform)
- 大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写
- 测试谷歌VS Code AI 编程插件 Gemini Code Assist
- 顾爷想知道第4.5期 国服便利性到底需优化啥?
- 掌握Visual Studio项目配置【基础篇】
- 还嫌LED驱动设计套路深?那就来看看这篇文章吧
- Visual Studio Community 2022(VS2022)安装图文方法
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)