【Linux学习笔记】11. Linux进程控制编程

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函数族可以参考这篇文章.

在新进程中执行一个新的程序需要两个步骤:

  1. 通过fork()系统调用创建一个新的进程;
  2. 通过exec()系统调用把新的二进制程序加载到该进程中。
4. 进程的终止

exit(), _exit()用于终止进程

区别:

  • exit():在停止进程之前,要检查文件的打开情况,并把文件缓冲区中的内容写回文件才停止进程。
  • _exit(): 直接使进程停止,清除其使用的内存,并清除缓冲区中的内容

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wlee2Fej-1651843497328)(https://cdn.jsdelivr.net/gh/Chen-Mxn/mx-picgo-image/20220506160749.png)]

因此,使用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来避免出现孤儿进程的情况。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值