1. 进程相关命令
这一部分查询的时候看就行,可以直接跳过,点击看后面的 2. 进程控制编程
查看系统进程
ps aux #查看系统中所有的进程,使用 BS 操作系统格式
ps -elf #查看系统中所有的进程,使用 Linux 标准命令格式,能看到优先级和父进程的PID
ps -l #只能看到shell产生的进程
实时查看系统进程
top -option
-option 选项: | 含义 |
---|---|
-d | 秒数:指定 top 命令每隔几秒更新。默认是 3 秒 |
-b | 使用批处理模式输出。一般和"-n"选项合用,用于把 top 命令重定向到文件中 |
-n | 次数:指定 top 命令执行的次数。一般和"-"选项合用 |
-p | 进程PID:仅查看指定 ID 的进程 |
-s | 使 top 命令在安全模式中运行,避免在交互模式中出现错误 |
-u | 用户名:只监听某个用户的进程 |
查看进程树
pstree -option PID或用户名
-option 选项: | 含义 |
---|---|
-a | 显示启动每个进程对应的完整指令,包括启动进程的路径、参数等。 |
-c | 不使用精简法显示进程信息,即显示的进程中包含子进程和父进程。 |
-n | 根据进程 PID 号来排序输出,默认是以程序名排序输出的。 |
-p | 显示进程的 PID。 |
-u | 显示进程对应的用户名称。 |
列出进程使用的文件
lsof -option
-option 选项 | 功能 |
---|---|
-c 字符串 | 只列出以字符串开头的进程打开的文件。 |
+d 目录名 | 列出某个目录中所有被进程调用的文件。 |
-u 用户名 | 只列出某个用户的进程打开的文件。 |
-p pid | 列出某个 PID 进程打开的文件。 |
调整进程优先级
进程的 nice 值,可以通过 nice 命令和 renice 命令修改,进而调整进程的运行顺序。
nice
命令可以给要启动的进程赋予 NI 值,但是不能修改已运行进程的 NI 值。
nice -n NI值 命令
NI值的范围为:-20~19
renice
命令可以在进程运行时修改其 NI 值,从而调整优先级。
renice NI值 PID
2. 进程控制编程
1. 获取进程ID
函数原型:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(); //获取当前进程ID
pid_t getppid();//获取当前进程的父进程ID
返回值是pid_t
类型,实际上也是short, int, long
整型,根据系统平台不一样会有不同的定义。
2. 创建进程fork()
函数原型:
#include <unistd.h>
pid_t fork(); //创建子进程
pid_t vfork(); //也是创建子进程,不过vfork创建的父子进程共享地址空间
返回值:
- 在父进程中,fork()返回新创建的子进程的PID
- 在子进程中,fork()返回0
- 出错时返回一个负值
其中,父进程和子进程的运行顺序随机。且创建出来的子进程有独立的地址空间。
当程序调用fork()时,系统会给创建出来的子进程分配地址空间,然后把父进程堆栈上的数据都拷贝到子进程中。这个拷贝的动作为写时拷贝。只要操作到内存时才会进行这个拷贝动作。
考虑下面这段代码:
int main()
{
int num = 0;
pid_t pid = fork();
if( -1 == pid) { perror("fork"); exit(1);}
else if( 0 == pid) num ++; //子进程动作
else num ++; //父进程动作
return 0;
}
这段代码的结果是父子进程中的 num 值都是1。且父子进程中的 num 的地址 &num
也是同一个。
为啥父子进程中的同一个地址变量却是独立的?留一个疑问,后面专门写一篇探讨,可以参考这篇博客和这篇博客。
此外,父进程的信号处理函数、文件描述符都会被子进程继承过去。比如父进程打开一个文件fd = 3
,那么在子进程中这个文件的描述符也是fd = 3
,并且使用的是与父进程同一个的文件指针。
3. exec函数族
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
fork创建子进程后执行的是和父进程相同的程序,尽管可以通过判断fork的返回值来执行不同的分支,更通用的做法是,子进程调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
作用通俗来说,就是使另一个可执行程序替换当前的进程,当我们在执行一个进程的过程中,通过exec函数使得另一个可执行程序A的数据段、代码段和堆栈段取代当前进程B的数据段、代码段和堆栈段,那么当前的进程就开始执行A中的内容,这一过程中不会创建新的进程,而且PID也没有改变。
exec函数族都是exec+l,p,v,e的组合,l表示命令行参数列表、p表示PATH环境变量、v表示使用参数数组、e使用环境变量数组。
关于返回值: exec函数一旦调用成功即执行新的程序,不返回。只有失败才返回,错误值errno为-1。所以通常我们直接在exec函数调用后直接调用perror()
和exit()
,无需if判断。
具体关于exec函数族可以参考这篇文章.
在新进程中执行一个新的程序需要两个步骤:
- 通过
fork()
系统调用创建一个新的进程; - 通过
exec()
系统调用把新的二进制程序加载到该进程中。
4. 进程的终止
exit()
, _exit()
用于终止进程
区别:
exit()
:在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。_exit()
: 直接使进程停止,清除其使用的内存,并清除缓冲区中的内容
因此,使用exit()
会更加安全。
5. 进程等待
函数原型:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status); //status保存子进程的退出状态
pid_t waitpid(pid_t pid, int *status, int options); //等待指定pid结束
功能:wait是一个阻塞函数,若父进程调用wait()
函数,则会等待子进程结束,然后回收子进程资源。同时可以根据status
的值来获取子进程的终止状态,判断是否正常退出。
因此可以利用wait来避免出现孤儿进程的情况。