LINUX 进 程 控 制

LINUX  进  程  控  制



1、进程简述


进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡的过程,是Linux 的基本调度单位。通过进程控制块( PCB)来描述的。进程控制块包含了进程的描述信息、控制信息以及资源信息,它是 进程的一个静态描述。

  • 进程标识
    • OS 会为每个进程分配一个唯一的整型 ID, 做为进程的标识号(pid)。 进程除了自身的 ID 外, 还有父进程 ID(ppid),所有进程的祖先进程是同一个进程, 它叫做 init 进程, ID 为 1, init 进程是内核自举后的一个启动的进程。 init 进程负责引导系统、 启动守护( 后台) 进程并且运行必要的程序。
    • 进程的 pid 和 ppid 可以分别通过函数 getpid()和 getppid()获得

  • 进程的用户 ID 与组 ID(进程的运行身份)
    • 进程在运行过程中, 必须具有一类似于用户的身份, 以便进行进程的权限控制, 缺省情况下, 哪个登录用户运行程序, 该程序进程就具有该用户的身份。
    • 真实用户 ID 和真实组 ID 可以通过函数getuid()和 getgid()获得。
    • 与真实 ID 对应, 进程还具有有效用户 ID 和有效组 ID 的属性, 内核对进程的访问权限检查时, 它检查的是进程的有效用户 ID 和有效组 ID, 而不是真实用户 ID 和真实组 ID。
    • 有效用户 id 和有效组 id 通过函数geteuid()和 getegid()获得。

  • s 权限提升 ,eg: a.out的可执行程序的文件所有者是 gotter,当执行
    chmod u+s a.out    (注意最后一个参数必须是可执行程序)后,此时已经提升权限
         之后, 我们切换用户ghaha并运行程序 a.out,此时,进程的有效用户身份变为了              
         ghaha,而不是 gotter 了, 这是因为文件 a.out 的访问权限 的所有者可执行为设置    
         了 s 的属性, 设置了该属性以后, 用户运行 a.out 时,a.out 进程的有效用户 身份将
         不再是运行 a.out 的用户, 而是 a.out 文件的所有者。
    • 修改密码就是最典型的应用例子。

  • 粘滞位权限 T  ------只用于目录,当用户删除时,检查的是目录真实的拥有者,而不是写权限。
  • S 是 chmod u-x a.out 之后的显示效果,
  • t 是 chmod o+t dir1, 可用于多人合作共享,只能删除自己的文件夹,但可以查看别人的文件夹。
  • T 是 chmod o-x dir1 之后的效果,变成T后,其他用户就不能进来了

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小例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
void Daemon()
{
     const int MAXFD=64;
     int i=0;
     if(fork()!=0)//父进程退出
          exit(0);
     setsid(); //成为新进程组组长和新会话领导, 脱离控制终端
     chdir("/"); //设置工作目录为根目录
     umask(0); //重设文件访问权限掩码
     for(;i<MAXFD;i++) //尽可能关闭所有从父进程继承来的文件
          close(i);
}
i nt main()
{
     Daemon(); //成为守护进程
     while(1)
     {
          sleep(1);
     }
     r eturn 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

smilejiasmile

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值