LINUX 进 程 控 制_进linux程控制

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2、进程的状态

1、执行态: 该进程正在运行, 即进程正在占用 CPU。

2、就绪态: 进程已经具备执行的一切条件, 正在等待分配 CPU 的处理时间片。

3、等待态: 进程不能使用 CPU, 若等待事件发生( 等待的资源分配到) 则可将其唤醒。

3、Linux 下的进程结构

Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。也就是说,进程之
间是分离的任务,拥有各自的权利和责任。其中,每个进程都运行在各自独立的虚拟地址空间,因此,
即使一个进程发生了异常,它也不会影响到系统的其他进程。

  • Linux 中的进程包含 3 个段, 分别为“数据段”、 “代码段”和“堆栈段”。
    • “数据段”放全局变量、 常数以及动态数据分配的数据空间。 数据段分成普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量) 、BSS 数据段(存放未初始化的全局变量) 以及堆( 存放动态分配的数据) 。
      • “代码段”存放的是程序代码的数据。
      • “堆栈段”存放的是子程序的返回地址、 子程序的参数以及程序的局部变量等。

4、Linux 下的进程管理

1、与进程管理相关的命令

        

注:

  • 进程 process: 是os的最小单元os会为每个进程分配大小为4g的虚拟内存空间, 其中 1g 给内核空间3g给用户空间{ 代码区 数据区 堆栈区}
  • ps –elf 查看所有的进程   ps –elLf 查看线程
  • ps -elf | grep 'aa’查找指定(aa) 进程
  • ps -aux 看%cpu(cpu 使用量) %mem( 内存使用量) stat 状态{ S 睡眠 T 暂停 R 运行 Z 僵尸}
  • [bg 作业 ID]可以将该进程带入后台运行
  • 利用 jobs 可以查看后台任务
  • fg 1 把后台任务带到前台, 这里的 1 表示作业 ID
  • kill -9 进程号表示向某个进程发送 9 号信号, 从而杀掉某个进程
  • renice 是改变优先级的  进程优先级有140个,从-40到99
  • 优先级默认值越低,其优先级越高。

2、进程的创建

Linux 下有四类创建子进程的函数: system(), fork(), exec*(), popen()

  • system 函数  (也可以用来嵌脚本)

    • #include <stdlib.h>
      • int system(const char *string);
      • system 函数通过调用 shell 程序/bin/sh –c 来执行 string 所指定的命令, 该函数在内部是通过调用execve(“/bin/sh”,…)函数来实现的。 通过 system 创建子进程后, 原进程和子进程各自运行, 相互间关联较少。 如果 system 调用成功, 将返回 0。
  • fork 函数    (非常重要)

    • #include <unistd.h>
      • pid_t fork(void);
      • 它从已存在进程中创建一个新进程。 新进程为子进程,而原进程为父进程。 它和其他函数的区别在于: 它执行一次返回两个值。其中父进程的返回值是子进程的进程号,而子进程的返回值为0.若出错则返回-1.因此可以通过返回值来判断是父进程还是子进程。
      • **注意:**使用 fork 函数得到的子进程是父进程的一个复制品, 它从父进程继承了进程的地址空间, 包括进程上下文、 进程堆栈、 内存信息、 打开的文件描述符、 信号控制设定、 进程优先级、 进程组号、 当前工作目录、 根目录、 资源限制、 控制终端,而子进程所独有的只有它的进程号、资源使用和计时器等。 通过这种复制方式创建出子进程后, 原有进程和子进程都从函数 fork 返回,各自继续往下运行, 但是原进程的 fork 返回值与子进程的 fork 返回值不同,在原进程中,fork返回子进程的pid,而在子进程中, fork 返回 0,如果 fork 返回负值, 表示创建子进程失败
  • exec 函数族

    • exec*由一组函数组成
      • int execl(const char *path, const char *arg, …)
      • exec 函数族的工作过程与 fork 完全不同, fork 是在复制一份原进程, 而 exec 函数是用 exec 的第一个参数指定的程序覆盖现有进程空间( 也就是说执行 exec 族函数之后, 它后面的所有代码不在执行)。
      • path 是包括执行文件名的全路径名
      • arg 是可执行文件的命令行参数, 多个用, 分割注意最后一个参数必须为 NULL。
  • popen 函数类似于 system 函数, 与 system 的不同之处在于它使用管道工作

    • #include <stdio.h>
      • FILE *popen(const char *command, const char *type);
      • int pclose(FILE *stream);
      • command 为可执行文件的全路径和执行参数;
      • type 可选参数为”r”或”w”,如果为”w”,则 popen 返回的文件流做为新进程的标准输入流,
      • 即 stdin,如果为”r”,则 popen返回的文件流做为新进程的标准输出流。
      • pclose 等待新进程的结束, 而不是杀新进程。

3、进程的控制

  • 如果父进程先于子进程退出, 则子进程成为孤儿进程, 此时将自动被 PID 为 1 的进程( 即 init) 接管。 孤儿进程退出后, 它的清理工作有祖先进程 init 自动处理。
  • 如果子进程先退出, 系统不会自动清理掉子进程的环境, 而必须由父进程调用 wait 或waitpid 函数来完成清理工作, 如果父进程不做清理工作, 则已经退出的子进程将成为僵尸进程(defunct),在系统中如果存在的僵尸( zombie) 进程过多, 将会影响系统的性能, 所以必须对僵尸进程进行处理。

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

pid_t waitpid(pid_t pid, int *status, int options);

wait 和 waitpid 都将暂停父进程, 等待一个已经退出的子进程, 并进行清理工作;

wait 函数随机地等待一个已经退出的子进程, 并返回该子进程的 pid;

waitpid 等待指定 pid 的子进程; 如果为-1 表示等待所有子进程。

status 参数是传出参数, 存放子进程的退出状态;

通常用下面的两个宏来获取状态信息:

WIFEXITED(status) 如果子进程正常结束, 它就取一个非 0 值。 传入整型值,非地址

WEXITSTATUS(status) 如果 WIFEXITED 非零, 它返回子进程的退出码

options 用于改变 waitpid 的行为, 其中最常用的是 WNOHANG, 它表示无论子进程是
否退出都将立即返回, 不会将调用者的执行挂起。

eg:

if(WIFEXITED(status))

{

printf(“the child exit value=%d\n”,WEXITSTATUS(status));

}else{

printf(“child crash\n”);

}

4、进程的终止

进程的终止有 5 种方式:

  • main 函数的自然返回;
  • 调用 exit 函数
  • 调用_exit 函数
  • 调用 abort 函数
  • 接收到能导致进程终止的信号 ctrl+c SIGINT ctrl+\ SIGQUIT

前 3 种方式为正常的终止, 后 2 种为非正常终止。 但是无论哪种方式, 进程终止时都

将执行相同的关闭打开的文件, 释放占用的内存等资源。

exit 和_exit 函数都是用来终止进程的。 当程序执行到 exit 和_exit 时, 进程会无条件

的停止剩下的所有操作, 清除包括 PCB 在内的各种数据结构, 并终止本程序的运行。

但是它们是有区别的。

exit 函数和_exit函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件, 就是图中的“ 清理 I/O 缓冲” 。比如有一些数据, 认为已经写入文件,实际上因为没有满足特定的条件,它们还只是
保存在缓冲区内, 这时用_exit 函数直接将进程关闭,缓冲区中的数据就会丢失。

  • exit 和_exit 函数的原型:
  • #include <stdlib.h> //exit 的头文件
  • #include <unistd.h> //_exit 的头文件
  • void exit(int status);
  • void _exit(int status);
  • status 是一个整型的参数, 可以利用这个参数传递进程结束时的状态。 一般来说, 0 表示正常结束; 其他的数值表示出现了错误,进程非正常结束。

5、用 fork 继承打开的文件

fork 以后的子进程自动继承了父进程的打开的文件, 继承以后, 父进程关闭打开的文

件不会对子进
程造成影响。相当于引用计数加1.

6、守护进程

Daemon 运行在后台也称作“后台服务进程”

  • 在 linux中,每一个系统与用户进行交流的界面称为终端, 每一个从此终端开始运行的进程都会依赖这个终端, 这个终端就称为这些进程的控制终端。 当控制终端被关闭时, 相应的进程都会自动关闭。
  • 但是守护进程却能突破这种限制, 它被执行开始运转, 直到整个系统关闭时才退出。几乎所有的服务器程序如 Apache 和 wu-FTP, 都用 daemon 进程的形式实现。
  • 很多 Linux下常见的命令如 inetd 和 ftpd, 末尾的字母 d 通常就是指 daemon。守护进程的特性

daemon 进程的创建编程规则

  • 创建子进程, 父进程退出,变成一个孤儿进程( orphan)

pid = fork();

if(pid>0)

exit(0);

  • 在子进程中创建新会话:使用系统函数 setsid()。
  • 由于创建守护进程的第一步调用了 fork 函数来创建子进程, 再将父进程退出。 由于在调用 fork 函数的时候, 子进程全盘拷贝了父进程的会话期、 进程组、控制终端等, 虽然父进程退出了, 但会话期、 进程组、 控制终端并没有改变, 因此, 还不是真正意义上的独立开来。 而调用 setsid 函数会创建一个新的会话并自任该会话的组长, 调用 setsid 函数有下面 3 个作用: 让进程摆脱原会话的控制, 让进程摆脱原进程组的控制, 让进程摆脱原控制终端的控制。进程组: 是一个或多个进程的集合。 进程组有进程组 ID 来唯一标识。 除了进程号(PID)之外, 进程组 ID( GID) 也是一个进程的必备属性。 每个进程都有一个组长进程, 其组长进程的进程号等于进程组 ID。 且该进程组 ID 不会因为组长进程的退出而受影响。
  • 会话周期: 会话期是一个或多个进程组的集合。 通常, 一个会话开始于用户登录, 终止于用户退出, 在此期间该用户运行的所有进程都属于这个会话期。
  • 控制终端: 由于在 linux 中, 每一个系统与用户进行交流的界面称为终端, 每一个从此终端开始运行的进程都会依赖这个控制终端。
  • 改变当前目录为根目录:
  • 使用 fork 函数创建的子进程继承了父进程的当前工作目录。 由于在进程运行中, 当前目录所在的文件是不能卸载的, 这对以后的使用会造成很多的不便。 利用 chdir(“/”);把当前工作目录切换到根目录
  • 重设文件权限掩码:
  • umask(0);将文件权限掩码设为 0,Deamon 创建文件不会有太大麻烦;
  • 关闭所有不需要的文件描述符
  • 新进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写, 而它们一直消耗系统资源。通常关闭从 0 到 MAXFILE 的所有文件描述符。
  • for(i=0;i<MAXFILE;i++)
  • close(i);
  • ( 注: 有时还要处理 SIGCHLD 信号 signal(SIGCHLD, SIG_IGN);防止僵尸进程( zombie) )

daemon小例子:

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值