linux进程原理

      进程是正在运行的程序!

             作者:下家山

 

一:实例解析

1.1 Win7是如何管理进程的?

 

1.2 linux中是如何查看任务管理器的

 PS命令

 

  

【Ps命令及参数解释参考《ps命令解析》】

1.3 看看自己写的程序在进程中是如何表现的

 

既然进程是正在运行的程序,我们就来写两个程序,看运行它和不运行它,一个用户运行它和两个用户运行它有什么区别!

1.3.1 代码

1.3.2 编译

1.3.3 一个用户运行两个进程

思考,为什么不是一个一个即时显示字符,而是一块一块显示?

查看进程

 

1.3.4 一个用户同时运行一个程序的情况

1.3.5 两个用户运行结果

 

 

1.4 总结:

1.4.1 linux上进程状态:

 


【进程状态参考《进程状态模型.doc》】

结合,裸板中的硬件中断来讲!

1.4.2 进程的结构

PID

每个进程都被分配一个唯一的数字编号,我们称为进程标识符【PID】。

PID的取值范围【2~32768】,当进程被启动时,系统将按顺序选择下一个未被使用的数字作为它的PID。当数字已经回绕一圈时,新的PID重新从2开始。【当我们在一个用户运行两个进程process1和process2的时候,看到系统连续分配的PID是4188和4189】

操作系统通过PID对进程进行管理,PID是进程表的索引。

 

进程运行的代码可以共享:

我们在【1.3.4】实验『一个用户同时运行同一个程序』时,我们看到有两个进程,但是我们的代码只有一份,这说明不管有多少个进程在运行某个程序,该程序是被共享的。

函数库(系统和标准)也可以被进程共享:

我们在process1.c中(process2.c同理)调用了printf函数,该函数是C语言的函数库,属于系统函数库,printf可以被所有进程同时调用。这样节省了磁盘空间!

进程所使用的变量独立存在【不能共享】:

这个实验代码,不足以证明这个问题。我进行一下修改:

这个程序运行时,打印的字符是我们运行前传进去的,并且保存在变量【 ch = argv[1][0];】中。

如果,有两个进程运行这个程序,那么,ch的值肯定不一样,这个例子有力的说明了进程中的变量是独立存在于每一个进程中。

进程所使用的堆栈独立存在【不能共享】:

我们把上面的程序改一下:

这个程序,当被某个进程运行时,传进去的参数,没有通过变量中转,直接使用,【参数是放在栈里面的】,如果有几个进程同时运行这个程序,那么每个进程所使用的堆栈是独立存在的,不是共享的。

【exit和return的区别参考《return和exit的区别》】

进程表:

  1. 进程表就像一个数据结构,它把当前加载在内存中的所有进程的有关信息保存在一个表中;
  2. 其中包括:进程的PID,进程的状态,命令字符串和其他一些PS命令输出的各类信息。
  3. 进程表的长度是有限制的,所以系统能够支持的同时运行的进程数也是有限制的。早期的UNIX系统只能同时运行256个进程。最新的实现版本已经大幅降低了这一限制,可以同时运行的进程数只与建立进程表的内存容量有关,而没有具体的数量限制。

【Ps执行结果就是从进程表中找到相关信息显示出来的。】

二:子进程,父进程,进程调度

理解亲缘关系

      

2.1 子进程:

一般而言,每个进程都是由另一个我们称之为父进程的进程启动的,被父进程启动的进程叫做子进程。

我们从ps -ef命令打印信息里面可以看到,PID是当前进程的进程ID,而PPID是当前进程的父进程ID。

2.2 init进程

我们知道,PID的取值范围是从2~32768,当我们执行ps -ef命令的时候,我们有没有发现 还有一个PID为1的进程?

什么是init进程呢?

 

Linux系统启动时,它将运行一个名为init的进程,该进程是系统运行的第一个进程,它的进程号为1。你可以把init进程看作是整个系统的进程管理器,是所有进程的祖先进程。我们所看到的所有进程,要么是init进程启动的,不么是被init进程启动的其他进程启动的。

2.3 父进程【0】

在Linux操作系统中,父进程也用PPID来进行管理。

我们在用ps -ef命令时,发现有PPID为的0父进程。这又是怎么回事呢?

 

Ps的另一种命令格式

2.4 进程调度

        当我们执行命令ps ax时看到如下结果

这里,2760号进程处于睡眠(等待,或阻塞)状态。

2867处于运行状态。

 

2.4.1 分时调度

在单处理器系统中,同一个时间只有一个进程在运行,其他进程处于等待状态。每个进程轮到的运行时间是非常短的(我们称之为时间片)是相当短暂的,这就给人一种多个程序在同时运行的假像。

2.4.2 时间片

2.4.3 调度器

Linux内核用进程调度器来决定下一个时间片应该分配给哪个进程。它的判断依据是进程的优先级(优先级1比优先级2的级别要高)。优先级高的进程运行得更为频繁。进程的运行时间不可能超过分配给他们的时间片,它们采用的是抢先式多任务处理,所以进程的挂起和继续运行无需彼此之间的协作。

2.4.4 nice程序

在一个如linux这样的多任务系统中,多个程序可能会竞争使用同一资源。在这种情况下,执行短期的突发性工作并暂停运行来等待输入的程序,要比持续占用处理器来进行计算或不断轮询系统来查看是否有新的输入到达的程序要好。

我们称表现良好的程序为nice程序,操作系统根据nice值来决定它的优先级,一个进程的nice值为0,并根据这个程序的表现不断变化。长期不间断运行的优先级一般会比较低。

例如:暂停来等待输入的程序会得到奖励。这可以帮助与用户进行交互的程序保存及时的响应性。在程序等待用户输入时,系统会增加它的nice值,使用renice命令调整它的值。Nice命令是将进程的nice值 增加10,从而降低该进程的优先级。

 

三,进程编程

3.1 启动新进程【system】

3.1.1 system执行shell命令

我们可以用system函数来编写一个程序,让它替我们来运行ps命令。

  

 

System(“ps -o pid,ppid,command”)相当于

$sh -c ps -o pid,ppid,command

我们这里命令./system1的PID是5255,它的父进程是4649,因为进程system1是由-bash进程启动的,即我们通过在shell提示符下输入./system1回车实现的。

而sh -c ps -o pid,ppid,command这个命令的PID是5256,它的父进程是5255,即由system1来启动的;

而命令ps -o pid,ppid,command这个命令的PID是5257,它由sh -c ps -o pid,ppid,command启动的,所以它的父进程ID是5256

3.1.2 system执行用户命令

   上面system运行的是shell命令(系统命令),我们可以运行用户自己的命令吗?

3.1.3 system执行后台命令

因为system函数用一个shell来启动想要执行的程序,所以可以把这个程序发到后台执行。具体做法如下:

实例解析:

 在第一个例子中,程序以ps -o pid,ppid,command为参数调用system函数,从而在程序中执行ps命令,整个程序在ps命令完成后才从system调用中返回。

 第二个例子中,我们运行的是我们自己编译出来的命令;

 这两个例子的优缺点:

 好处:能够在代码中去执行某一个shell或者用户命令,不需要shell命令提示符。

 缺点:程序必须等待system函数启动的进程结束后才能继续,因此我们不能立刻执行其他任务。

 在第三个例子中,对system函数的调用将在shell命令( ps -o pid,ppid,command &)交给系统后立即返回。由于它是一个在后台运行程序的请求,所以ps程序一启动shell就返回了,这与我们在shell提示符下执行下面这条命令的效果是一样的:

$ps -o pid,ppid,command &

困扰:

在ps命令还未来得及打印出它的输出结果之前,system3程序就打印出字符串Done然后退出了。在system3程序退出后,ps命令继续完成它的输出。这类的处理往往会给用户带来很大的困扰。输出信息乱了!

如果想要用好进程,我们就需要对他们的行为进行细致的控制。

3.2 替换进程映像【exec函数】

先看例程:

例程解析:

程序先打印它的第一条语句:Running ps -o pid,ppid,command with execlp

接着调用execlp,execlp这个函数在当前环境变量(PATH)里面寻找我们所给参数中的第一个参数”ps”为命令进行执行,并以第二个参数为命令参数。就好象我们在shell提示符下执行了

$ps -o pid,ppid,command

Ps命令结束时,整个execlp程序结束,退出到shell命令提示符状态。

并没有返回到execlp函数,打印第二条语句Done。

事实上,这里发生的是,当execlp启动了ps -o pid,ppid,command后,ps 进程替代了execlp进程。Ps进程作为新进程,其pid,ppid,和nice值与原先execlp进程一样。

Execlp函数一带成功过渡到参数里面的进程,就不会返回!

 

【常量指针与指针常量问题,参考《常量指针_指针常量.doc》】

【Exec函数的详细介绍,参考《Exec函数.doc》】

3.3 复制进程【fork】

 

要想让进程同时执行多个函数,我们可以使用线程或从原程序中创建一个完全分离的进程,后者就像init的做法一样,而不像exec调用那样用新程序替换当前执行的进程。

我们可以通过调用fork创建一个新进程。这个系统调用复制当前进程,在进程表中创建一个新的表项,新的表项中的许多属性与当前进程是相同的。新进程几乎与原进程一模一样,执行的代码也完全相同,但新的进程有自己的数据空间,环境和文件描述符。

3.3.1 头文件与函数原型

#include <sys/types.h>

#include <unistd.h>

Pid_t fork(void);

3.3.2 函数功能

Fork函数调用,复制当前进程,从而产生一个新进程。被复制的进程叫父进程。子进程除了有自己的数据空间,环境和文件描述符,进程表项中的其他部分包括执行代码与父进程一模一样。

返回值:

Fork函数调用一次返回两个值,如果返回值为0,那么是子进程代码返回的,如果返回值大于0,那么是父进程所运行的代码返回的。

 

3.3.3 例程:

3.3.4 例程解析:

这个程序以两个进程的形式运行。

子进程被创建并且输出消息5次。

原进程(即父进程)只输出消息3次。

这样,造成的现象是:父进程在子进程打印完它的全部消息之前就结束了,因此我们将看到在输出内容中混杂着一个shell提示符。

3.4 等待一个进程【wati】

当用fork启动一个子进程时,子进程就有了自己的生命周期并独立运行。

有时,我们希望知道子进程何时结束。例如,上上面的例子中,父进程在子进程之前结束,由于子进程还在继续运行,所以得到的输出结果有点乱。

我们可以在父进程中调用wait函数让父进程等待子进程的结束。

3.4.1 wait函数原型及头文件

 #include <sys/types>

 #include <sys/wait.h>

 Wait系统调用将暂停父进程直到它的子进程结束为止。

 

 Pid_t wait(int *stat_loc);

3.4.2 wait参数

参数*stat_loc用来存放子进程的状态信息,即子进程的退出状态,具体来讲就是子进程的main函数返回的值或子进程中exit函数【参考《return和exit的区别.doc》】的退出码。

如果stat_loc不是空指针,状态信息将被写入它所指向的位置。

 

在sys/wait.h中定义了宏来解释子进程退出的状态信息。

宏定义

含义

WIFEXITED(status)

如果进程通过系统调用_exit或函数调用exit正常退出,该宏的值为真。

WIFSIGNALED(status)

如果子进程由于得到的信号(signal)没有被捕捉而导致退出时,该宏的值为真。

WIFSTOPPED(status)

如果子进程没有终止,但停止了并可以重新执行时,该宏返回真。这种情况仅出现在waitpid调用中使用了WUNTRACED选项。

WEXITSTATUS(status)

如果WIFEXITED(status)返回真,该宏返回由子进程调用_exit(status)exit(status)时设置的调用参数status值。

WTERMSIG(status)

如果WIFSIGNALED(status)返回为真,该宏返回导致子进程退出的信号(signal)的值。

WSTOPSIG(status)

如果WIFSTOPPED(status)返回真,该宏返回导致子进程停止的信号(signal)值。

 

 

3.4.3 wait返回值

这个调用返回子进程的PID,它通常是已经结束运行的子进程的PID。

 

3.4.4 例程:

我们可以通过ps el查看fork出来的子进程的ID

【注意:】

输出语句Child exited with code 37是由

Printf(“Chiled exited with code &d\n”,WEXITSTATUS(stat_val));实现的。

其中的37来自于子进程代码:

Exit(exit_code);当我们调用fork函数时,创建的新进程,也有一份与原进程一致的代码,所以exit(exit_code);这段代码父进程和子进程都有。

3.4.5 实例解析

父进程(fork返回大于0的那个值)用wait系统调用将自己挂起,直到子进程的状态信息出现为止。这将发生在子进程调用exit的时候。我们将子进程的退出码设置为37.父进程继续运行,通过WIFEXITED(stat_val)的返回值来判断子进程是否正常终止。如果是,就从状态信息(stat_val)中提出子进程的退出码。

3.5 僵尸进程

3.5.1 先看实例

3.5.2 实例解析

  

我们在子进程代码(fork返回值为0)和父进程代码(fork返回值大于0)中都调用了ps -o pid,ppid,state,tty,command。

子进程调用ps命令,输出结果中,因为父子进程此时都没有退出,属于正常情况;

但是,当父进程调用ps命令的时候,,此时子进程已经退出了【退出原因是因为父进程通过sleep阻塞了自己,让子进程提前自己结束】,在这种情况下,子进程成为了僵尸进程。

 

3.5.3 什么是僵尸进程:

一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

3.6 孤儿进程

3.6.1 先看实例

3.6.2 实例分析

子进程两次调用getpid,getppid函数,

第一次调用打印结果

PID:5987  PPID:5986【注意,如果在父进程中不调用sleep,子进程第一次打印结果有可能获取不到ppid的值,因为父进程有可能在打印语句调用之前结束】

第二次调用,是在睡眠5秒之后(在子进程中),之所以睡眠5秒,是想保证父进程退出。这样子进程变成孤儿进程,由init(进程号为1)接管。

3.6.3 什么是孤儿进程

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

3.7 僵尸与孤儿进程的问题及危害

   僵尸进程&孤儿进程的危害

附录1  group passwd文件解析

1.1 group

1.2 passwd

从上面的例子我们可以看到,/etc/passwd中一行记录对应着一个用户,每行记录又被冒号(:)分隔为7个字段,其格式和具体含义如下:
用户名:口令:用户标识号:组标识号:注释性描述:主目录:登录Shell
“用户名”是代表用户账号的字符串。通常长度不超过8个字符,并且由大小写字母和/或数字组成。登录名中不能有冒号(:),因为冒号在这里是分隔符。为了兼容起见,登录名中最好不要包含点字符(.),并且不使用连字符(-)和加号(+)打头。
“口令”一些系统中,存放着加密后的用户口令字。虽然这个字段存放的只是用户口令的加密串,不是明文,但是由于/etc/passwd文件对所有用户都可读,所以这仍是一个安全隐患。因此,现在许多Linux系统(如SVR4)都使用了shadow技术,把真正的加密后的用户口令字存放到/etc/shadow文件中,而在/etc/passwd文件的口令字段中只存放一个特殊的字符,例如“x”或者“*”。
“用户标识号”是一个整数,系统内部用它来标识用户。一般情况下它与用户名是一一对应的。如果几个用户名对应的用户标识号是一样的,系统内部将把它们视为同一个用户,但是它们可以有不同的口令、不同的主目录以及不同的登录Shell等。
通常用户标识号的取值范围是0~65535。0是超级用户root的标识号,1~99由系统保留,作为管理账号,普通用户的标识号从100开始。在Linux系统中,这个界限是500。
“组标识号”字段记录的是用户所属的用户组。它对应着/etc/group文件中的一条记录。
“注释性描述”字段记录着用户的一些个人情况,例如用户的真实姓名、电话、地址等,这个字段并没有什么实际的用途。在不同的Linux系统中,这个字段的格式并没有统一。在许多Linux系统中,这个字段存放的是一段任意的注释性描述文字,用做finger命令的输出。
“主目录”,也就是用户的起始工作目录,它是用户在登录到系统之后所处的目录。在大多数系统中,各用户的主目录都被组织在同一个特定的目录下,而用户主目录的名称就是该用户的登录名。各用户对自己的主目录有读、写、执行(搜索)权限,其他用户对此目录的访问权限则根据具体情况设置。
用户登录后,要启动一个进程,负责将用户的操作传给内核,这个进程是用户登录到系统后运行的命令解释器或某个特定的程序,即Shell。Shell是用户与Linux系统之间的接口。Linux的Shell有许多种,每种都有不同的特点。常用的有sh(BourneShell),csh(CShell),ksh(KornShell),tcsh(TENEX/TOPS-20typeCShell),bash(BourneAgainShell)等。系统管理员可以根据系统情况和用户习惯为用户指定某个Shell。如果不指定Shell,那么系统使用sh为默认的登录Shell,即这个字段的值为/bin/sh。
用户的登录Shell也可以指定为某个特定的程序(此程序不是一个命令解释器)。利用这一特点,我们可以限制用户只能运行指定的应用程序,在该应用程序运行结束后,用户就自动退出了系统。有些Linux系统要求只有那些在系统中登记了的程序才能出现在这个字段中。

 

 

发布了38 篇原创文章 · 获赞 14 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_27320195/article/details/85236642
今日推荐