浅谈Linux进程

Linux进程的概念:

简单来说就是运行的程序,只不过程序是保存在硬盘上的可执行的代码。Linux一个重要的特点是可以同时启动多个进程。它是操作系统资源管理的最小单位。(我们可以通过ps和pstree查看进程和进程树)每个进程有一个进程描述符PID。


Linux进程的结构:

分为代码段,数据段和堆栈段三部分。等同于我们说的在程序运行的时候的组成。


Linux进程状态:

R:运行状态        S:可中断等待状态(进程在等待某个事件完成,在这之中也可以被信号或者计时器唤醒)

D:不可中断等待状态(进程必须等待某个时间完成) Z:进程虽然结束,但是其PID依然存在。

T:停止状态(如在调试或者收到SIGSTOP等信号)

后缀字符含义:<(高级优先级进程),N(低级优先级进程),L(内存锁页),s(该进程为会话首进程),l(多线程进程),+(进程位于前台进程组)


进程的控制操作:

进程的创建:

触发一个事件时,系统会定义成为一个进程。我们系统只识别二进制文件,所以这就是我们启动的二进制程序。当这个事件触发完成后,他会加载到内存。在内存中产生被分配的PID,执行者的权限属性参数,程序所需代码与相关资料等数据。进程的创建依赖fork()函数。fork函数有两个返回值,分别返回子进程ID和0,分别代表当前进程是父进程或者子进程。在执行的时候父子进程是交替执行的。

简介一下用户ID与组ID:

实际用户ID(uid) :标识运行该进程的用户

有效用户ID(euid):标识以什么用户身份来运行进程,例如,某个普通用户A,运行了一个程序,而这个程序是以root身份来运行的,那么这个程序运行时就有root权限。此时实际用户ID是A用户的ID,有效用户ID是root用户ID

实际组ID(gid):实际用户所属组ID

有效组ID(egid):有效组ID是有效用户所属的组ID


再多谈一点fork的工作流程:

Linux通过clone()系统调用实现fork(),然后由clone调用do_fork(),do_fork完成了创建中的大部分工作。该函数调用copy_process函数,然后让进程开始运行下面解释一下copy_process的作用

(1)调用函数为新进程创建一个内核栈,此时子进程和父进程描述符是完全相同的。

(2)检查并确保新创建这个子进程后,当前用户所拥有的进程数目没有超出给它分配的资源的限制。

(3)子进程着手使自己与父进程区分开来,进程描述符内的许多称愿都要被清0或者被设为初始值,那些不是继承而来的进程描述符称愿主要是统计信息,仍未被更改。

(4子进程的状态被设置为保证它不会进行的状态。

(5)copy_process调用copy_flags以更新task_struct的flags成员。表明进程是否拥有超级用户权限的PF_SUPERRPIV标志被清为0,表明进程还没有调用exec函数的PF_FORKNOEXEC标志被设置。

(6)调用alloc_pid为新进程分配一个有效的PID

(7)根据传递给clone的参数标志,copy_process拷贝或共享打开文件,文件系统信息,信号处理函数,进程地址空间和命名空间等。一般情况下,这写资源会被给定进程的所有线程共享,否则,这些资源队每个进程都是不同的,因此被拷贝到这里。

(8)最后的扫尾工作后,返回一个指向子进程的指针,返回do_fork函数,新进程被唤醒并被投入运行


对于子进程与父进程,除了PID,文件锁,警告等一些数据外,其他数据子进程都会继承父进程。简单说,就是复制一份。


孤儿进程:

父进程比子进程先结束,子进程被PID为1的init收养,当然有的系统是被upstart收养,在孤儿进程结束的时候,init会将其释放。至于为什么ubuntu

僵尸进程:
僵尸进程:一个子进程在其父进程还没有调用wait()或waitpid()的情况下退出。这个子进程就是僵尸进程。任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程.


15.04的孤儿程序为什么被upstart收养,这个原因可以参考我的这篇日志:http://blog.csdn.net/chilumanxi/article/details/47066331


vfork函数本是为了节省地址空间,以免fork后接着exec导致的多余复制操作。vfor的子进程不会拷贝父进程的地址空间而是共享地址空间。这就导致子进程对全局变量的操作对父进程是可见的。


守护进程的创建:

用fork调用后使父进程退出,使子进程成为孤儿程序。此后用setsid创建一个新的对话期,使进程成为一个会话组长。因为子进程复制了父进程的会话期和进程组,这样就可以摆脱原来的进程组与会话期。然后为了保证该进程重新打开终端,所以要让他不是会话组长,方法是创建一个新的进程后关掉父进程。此后的清理工作就是关掉不需要的从父进程继承的文件描述符。将当前目录更改为根目录。将文件的屏蔽字设置为0。这样一个守护进程便创建了。

守护进程的特点:

独立于控制终端。通常周期性的执行某种任务。


进程的退出:

正常退出的话只需要调用exit或者_exit即可。两者具体差别参考我的另一篇博文:http://blog.csdn.net/chilumanxi/article/details/47050969

异常退出的话,调用about函数或者收到信号停止。


执行新的程序:exec()函数族

一旦调用exec函数,系统会将代码替换成新的程序代码,废弃原有的数据段和堆栈段,并为新程序分配新的程序段,唯一保留的就是程序ID,也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。exec函数一般没有返回值,因为一旦执行,就是另外一个程序,不再有程序可以接收到这个返回值了。但是对于exec错误的话,会返回-1.通常这样的错误是由文件名或参数错误引起的。execl调用shell 时,要在shell脚本中指明使用的shell版本:#!/bin/bash。在命令行下执行shell脚本,系统为它自动打开一个shell,在程序中没有shell,在调用shell脚本时,会出错,所以要在shell脚本中先打开shell。

关于环境变量:PATH环境变量包含了一张目录表,系统通过PATH环境变量定义的路径搜索执行码,PATH环境变量定义时目录之间需用用“:”分隔,以“.”号表示结束。PATH环境变量定义在用户的.profile或.bash_profile中

等待进程的结束:

通过wait和waitpid函数。

wait函数使父进程暂停执行,直到它的一个子进程结束为止。返回值是终止的进程的PID。

waitpid也用来等待子进程的结束,但它用于等待某个特定进程的结束。参数pid指明要等待的子进程的PID。waitpid提供了一个wait的非阻塞版本,这个选项是:WNOHANG。如果如果想周期性地检查某个特定的子进程是否已经退出可以这样调用waitpid:

waitpid(child_pid,  (int *)0,  WNOHANG)

获得进程的ID:

getpid函数

设置用户ID与组ID:

setuid与setgid:

setuid()函数:设置用户ID,若进程有root权限,则函数将实际用户和有效用户都设置为uid,若进程不具有root权限的话但是uid等于实际用户的uid的话,则只将有效用户ID改为uid。如果两个条件都不满足的话,则返回-1报错。

内核对进程读取文件的许可权检查,是通过考查进程的有效用户ID来实现的。

改变进程的优先级:

getpriority()函数:该函数返回一组进程的优先级,结合参数which和who可组合去顶返回哪一组进程的优先级。当制定的一组进程的优先级不同的时候,函数将返回其中优先级最低的一个。他的which参数有:PRIO_PROCESS 一个特定的进程,此时who取值为进程ID PRIO_PGRP 一个进程组的所有ID,此时who为进程组ID,PRIO_USER:一个用户用有的所有进程,who取值为实际用户ID。

setpriority()函数:该函数用来设置进程的优先级。

nice函数等于调用上述两个函数。

一般用户只能将nice值越调越高


转载于:https://www.cnblogs.com/chilumanxi/p/5136100.html

你可能感兴趣的:(shell,运维,操作系统)