Linux系统编程之进程介绍

在这里插入图片描述

达者为先  师者之意


1 进程的概述

Linux系统编程——进程
进程是程序的动态的执行过程。程序是静态的过程
程序: 存储在磁盘上的一堆指令。
进程: 将程序动态的运行起来,就转成进程,(即程序的执行过程)过程: 包含进程的创建,进程的运行以及进程消亡 。

  • Linux系统是一个多进程的系统,它的进程之间具有互不干扰等行性、特点。也就是说,进程之间是分离的任务,拥有各自的权利和责任。其中,每个进程都运行在各自独立的虚拟地址空间,因此,即使一个进程发生了异常,它也不会影响到系统的其他进程。
  • 当创建一个进程,系统会分配0—4G 虚拟地址空间,其中0—3G是用户空间 3—4G内核空间 。多个进程共用同一份内核, 各自进程的用户空间是独立的 。
  • 空间的划分是由内核来决定,0—3G用户空间3—4
    内核空间,这个划分是内核默认 。

在这里插入图片描述

  • 代码段: 存放字符常量
  • 数据段: 初始化后的全局变量 只读数据段: const 所修饰的变量
  • bss数据段: 未被初始化的全局变量
  • 堆:程序员自己手动申请的空间 malloc -------- 小栈 大堆 思想 。
  • 栈:存放函数内部所包含的局部变量以及函数。

进程的类型:

  • 交互进程 :shell 终端 就是交互进程 。
  • 批处理进程 :通过 ps –aux 来进行查看到的进程 。
  • 守护进程 :随系统的启动开始运行,系统关闭的时候,结束运行,在运行期间不会停止运行。在linux 操作系统下面守护进程不受终端的控制 。

2 ps命令

ps 命令是最常用的监控进程的命令,通过此命令可以查看系统中所有运行进程的详细信息。

ps 命令有多种不同的使用方法,这常常给初学者带来困惑。在各种 Linux 论坛上,询问 ps 命令语法的帖子屡见不鲜,而出现这样的情况,还要归咎于 UNIX 悠久的历史和庞大的派系。在不同的 Linux 发行版上,ps 命令的语法各不相同。为此,Linux 采取了一个折中的方法,即融合各种不同的风格,兼顾那些已经习惯了其它系统上使用 ps 命令的用户。

ps 命令的基本格式如下:

[root@localhost ~] # ps aux
#查看系统中所有的进程,使用 BS 操作系统格式
[root@localhost ~] # ps -le
#查看系统中所有的进程,使用 Linux 标准命令格式

选项:

  • a:显示一个终端的所有进程,除会话引线外;
  • u:显示进程的归属用户及内存的使用情况;
  • x:显示没有控制终端的进程;
  • -l:长格式显示更加详细的信息;
  • -e:显示所有进程;
    可以看到,ps 命令有些与众不同,它的部分选项不能加入"-“,比如命令"ps aux”,其中"aux"是选项,但是前面不能带“-”。

大家如果执行 “man ps” 命令,则会发现 ps 命令的帮助为了适应不同的类 UNIX 系统,可用格式非常多,不方便记忆。所以,我建议大家记忆几个固定选项即可。比如:

  • “ps aux” 可以查看系统中所有的进程;

  • “ps -le” 可以查看系统中所有的进程,而且还能看到进程的父进程的 PID 和进程优先级;

  • “ps -l” 只能看到当前 Shell 产生的进程;

  • 注意:还可以用 grep [选项] [文件或目录]…过滤/搜索的特定字符
    如"ps aux|grep init" 查找 init 字段的进程

有这三个命令就足够了,下面分别来查看。
在这里插入图片描述
表1 罗列出了以上输出信息中各列的具体含义
在这里插入图片描述

"ps aux"命令可以看到系统中所有的进程,"ps -le"命令也能看到系统中所有的进程。由于 “-l” 选项的作用,所以 “ps -le” 命令能够看到更加详细的信息,比如父进程的 PID、优先级等。但是这两个命令的基本作用是一致的,掌握其中一个就足够了。
在这里插入图片描述
表 2 罗列出以上输出信息中各列的含义
在这里插入图片描述

如果不想看到所有的进程,只想查看一下当前登录产生了哪些进程,那只需使用 “ps -l” 命令就足够了:
在这里插入图片描述
可以看到,这次从 pts/1 虚拟终端登录,只产生了两个进程:一个是登录之后生成的 Shell,也就是 bash;另一个是正在执行的 ps 命令。

3 top命令

ps 命令可以一次性给出当前系统中进程状态,但使用此方式得到的信息缺乏时效性,并且,如果管理员需要实时监控进程运行情况,就必须不停地执行 ps 命令,这显然是缺乏效率的。
为此,Linux 提供了 top 命令。top 命令可以动态地持续监听进程地运行状态,与此同时,该命令还提供了一个交互界面,用户可以根据需要,人性化地定制自己的输出,进而更清楚地了进程的运行状态。

top 命令的基本格式如下:
[root@localhost ~] #top [选项]

选项:

  • -d 秒数:指定 top 命令每隔几秒更新。默认是 3 秒;
  • -b:使用批处理模式输出。一般和"-n"选项合用,用于把 top 命令重定向到文件中;
  • -n 次数:指定 top 命令执行的次数。一般和"-"选项合用;
  • -p 进程PID:仅查看指定 ID 的进程;
  • -s:使 top 命令在安全模式中运行,避免在交互模式中出现错误;
  • -u 用户名:只监听某个用户的进程;

在 top 命令的显示窗口中,还可以使用如下按键,进行一下交互操作:

  • ? 或 h:显示交互模式的帮助;
  • P:按照 CPU 的使用率排序,默认就是此选项;
  • M:按照内存的使用率排序;
  • N:按照 PID 排序;
  • T:按照 CPU 的累积运算时间排序,也就是按照 TIME+ 项排序;
  • k:按照 PID 给予某个进程一个信号。一般用于中止某个进程,信号 9 是强制中止的信号;
  • r:按照 PID 给某个进程重设优先级(Nice)值;
  • q:退出 top 命令;

4 进程标识码

函数原型:

#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);

在这里插入图片描述
pid_t 的实质是int类型的。
getpid()返回当前进程标识码,getppid()返回父进程标识。

5 fork与vfork函数

fork函数

我们都知道fork可以用于进程的创建,那首先我们来了解一下fork函数原型和基本的两种用法才真正的了解我们何时才会用到fork来进行进程的创建。

函数原型:

#include<unistd.h>
pid_t fork(void)

参数含义:无参传入,返回pid_t类型的数值。pid_t 的实质是int类型的。
fork函数被调用一次,但是返回两次
子进程返回的是0,而父进程返回值则是新子进程的进程ID。
注意:父进程返回值是新进程的进程ID
因为一个进程的子进程可以有多个,并且没有一个函数是一个进程可以获得其所有子进程的进程ID。父进程中没有记录子进程的pid。所以为了方便父进程知道和处理子进程,fork()返回最新子进程的pid。

用法一

  • 一个父进程希望复制自己,使父、子进程同时执行不同的代码段。
    这在网络服务进程中是最常见的一种处理,父进程等待客户端的服务请求,当达到请求到达时,父进程调用fork,使子进程处理此请求。父进程则等待下一个服务请求到达。

用法二

  • 一个进程要执行一个不同的程序
    这个用法对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

vfork函数(尽量不要使用)

函数原型:

#include<unistd.h>
pid_t vfork(void)  // 返回值pid_t 的实质是int类型的
  1. 不进行空间拷贝(写时都不拷贝),子进程pcb直接指向父进程pcb指向的位置
  2. 父进程一定是等到子进程执行完毕以后在执行
  3. 必须调用exit 或exec系列的函数,否则就会出现段错误 4、任何一个系统上实现的vfork或多过少的都有问题,尽量 不要使用

vfork()注意:
子进程先执行,不能return返回,return是在堆栈上进行操作,return返回相当于压栈一次,出栈两次,即子进程释放了main的栈,父进程再去释放就形成了段错误。

总结:fork和vfork的区别

  • 调用fork后, 父子进程运行顺序不定,而vfork子进程一定先运行。
  • fork存在写时拷贝的机制,子进程拷贝父进程地址空间。 vfork子进程(在调用exec(进程替换)或者exit之前)共享父进程的地址空间(一改都改)。
  • vfork必须调用exit返回,否则会引发段错误。

6 进程的退出

Linux 下进程的退出分为正常退出和异常退出两种:

  • 正常退出
    a. 在main()函数中执行return 。
    b.调用exit()函数
    c.调用_exit()函数
  • 异常退出
    a.调用abort函数
    b.进程收到某个信号,而该信号使程序终止,比如Ctrl+c,进程收到某个信号,而该信号是程序中止。

但不管是哪种退出方式,系统最终都会执行内核中的某一代码。这段代码用来关闭进程所用已打开的文件描述符,释放它所占用的内存和其他资源。

几种退出方式的比较

  1. exit和return 的区别:
    exit是一个函数,有参数。exit执行完后把控制权交给系统
    return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

  2. exit和abort的区别:
    exit是正常终止进程
    abort是异常终止。

关于exit函数
函数原型

#include <stdlib.h>
void exit(int status);

exit函数的参数:当前进程要返回的退出码
注意:status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其它的值表示出现了错误,进程非正常结束。

函数说明

  • 直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。exit(0)表示正常退出,exit(x)(x不为0)表示异常退出,这个x是返回给操作系统(包括UNIX,Linux,和MS DOS)的,以供其他程序使用。

关于_exit函数
函数原型

#include <unistd.h>
void _exit(int  status)

注意:_exit 函数的参数:当前进程要返回的退出码
status是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其它的值表示出现了错误,进程非正常结束。
_Exit函数等效于 _exit函数。
函数说明

  • _exit函数会立即终止调用过程。属于该进程的任何打开的文件描述符都被关闭;进程的任何子进程都由init进程(初始化进程,进程ID:1)继承,进程的父进程将被发送一个SIGCHLD信号。值状态作为进程的退出状态返回给父进程,并且可以使用wait(2)系列调用之一收集。

exit()和_exit()函数的区别
exit和_exit函数都是用来终止进程的。当程序执行到exit或_exit时,系统无条件的停止剩下所有操作,清除各种数据结构,并终止本进程的运行。

  • exit在头文件stdlib.h中声明,而_exit()声明在头文件unistd.h中声明。 exit中的参数exit_code为0代表进程正常终止,若为其他值表示程序执行过程中有错误发生。
  • _exit()执行后立即返回给内核,而exit()要先执行一些清除操作,然后将控制权交给内核。
  • 调用_exit函数时,其会关闭进程所有的文件描述符,清理内存以及其他一些内核清理函数,但不会刷新流(stdin, stdout, stderr …). exit函数是在_exit函数之上的一个封装,其会调用_exit,并在调用之前先刷新流。
  • exit()函数与_exit()函数最大区别就在于exit()函数在调用exit系统之前要检查文件的打开情况,把文件缓冲区的内容写回文件。由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。

exit 最后也会调用 _exit函数,但exit在中止进程的同时,还会刷新缓冲区、关闭流等
如下图:
在这里插入图片描述

7 进程等待

前面提到这么一个问题
父进程在忙,子进程结束了,但无人回收,这样就造成了“死亡”的子进程一直占用资源,这个时候的子进程被称为“僵尸进程”
为了解决这个问题,最初的思路是:让父进程停下,等待子进程执行完,然后回收子进程,清理掉的子进程ID。

进程等待的必要性

  • 子进程退出,父进程如果不管不顾,就可能造成“僵尸进程”的问题,进而造成内存泄漏。

  • 进程一旦变成僵尸状态,就会刀枪不入,kill -9 强杀也无能为力,

  • 父进程派给子进程的任务完成的如何,父进程得知道,

  • 父进程通过进程等待的方式,回收子进程的资源,获取子进程的退出信息。

注意 : 当子进程执行时, wait()可以暂停父进程的执行,使其等待。一旦子进程执行完,等待的父进程就会重新执行。如果有多个子进程在执行,那么父进程中的 wait()在第一个子进程结束时返回,恢复父进程执行。

进程等待的方法(如何让父进程进行进程等待):
wait函数和waitpid函数
wait函数:
函数原型

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int* status)

返回值 pid_t : 成功返回被等待进程的 PID,失败返回 -1。
参数 status: 参数是一个指针类型,但是该指针类型并不是要传递一个指针参数,而是一个输出型参数 ; 获取子进程的退出状态,不关心则可以设置为 NULL。调用函数时不用传入具体的值,在函数内部赋予值,在函数返回之后,调用者就可以拿到函数当中赋予的值。

子进程的结束状态返回后存于status,底下有几个宏可判别结束情况

  • WIFEXITED(status)如果子进程正常结束则为非0值。
  • WEXITSTATUS(status)取得子进程exit()返回的结束代码,一般会先用 WIFEXITED 来判断是否正常结束才能使用此宏。
  • WIFSIGNALED(status)如果子进程是因为信号而结束则此宏值为真
  • WTERMSIG(status)取得子进程因信号而中止的信号代码,一般会先用 WIFSIGNALED 来判断后才使用此宏。
  • WIFSTOPPED(status)如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况。
  • WSTOPSIG(status)取得引发子进程暂停的信号代码。

wait函数的四个特性:

1.输出型参数,与其对应的有:

  • 输入型参数→调用者给被调用函数传参;
  • 输入输出型参数;

2.int* status是一个指针类型占四个字节,但是实际中只使用到后两个字节,将这两个字节分为三部分:

  • 退出码 + coredump标志位 + 退出信号
  • 高位字节表示退出码;
  • 低位的一个字节又被分为两个部分,
  • 第一个比特位表示coredump标志位
  • 后七个比特位表示退出信号
    在这里插入图片描述

3.退出码:程序正常退出时用到

  • main函数的return返回时,return后的数字就是退出码
  • exit和_exit中的参数就是退出码

4.coredump标志位,退出信号是程序异常退出时用到:

  • coredump为0表示该进程在退出时没有内存镜像文件的产生(也就是说没有coredump产生),
  • 为1则有镜像文件产生,当前程序退出异常
  • 退出信号:进程异常退出时收到的几号信号
  • 进程正常退出时coredump标志位和退出信号被置为全0;
    退出码则是进程正常退出给的数值;
  • 异常退出coredump标志位和退出信号会有相应的数;
    也就是说程序正常退出只有退出码起作用,异常退出则要看标志位和退出信号

waitpid 函数
函数原型

#include <sys/types.h>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大红烧肉

赠人玫瑰,手留余香。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值