进程基础

来源:互联网 发布:办公室软件视频教程 编辑:程序博客网 时间:2024/06/16 15:36


1. 进程基础

了解进程的概念。

了解进程和程序的区别

进程的类型

进程的运行状态以及之间的转化

进程的模式

1.1进程的相关基本概念(P5)

回顾一下:我们可以把操作系统看做应用程序和硬件之间的一层软件。

1) 防止硬件被失控的应用程序滥用。

2) 为应用程序提供简单一致的接口来访问底层的功能各异的硬件。操作系统将前述的计算机系统抽象为几个基本的抽象概念:

a) 进程:为每个应用程序虚拟独占整个计算机系统。

b) 虚拟存储器:为每个进程提供了一个假象好像独占内存,而且提供了一个抽象的虚拟地址空间,使每个进程看到的存储器都是一致的。想想看如果没有这种虚拟机制我们的链接器的实现会多么麻烦。

c) 文件:……

进程和程序的区别:

程序是一个静态的概念,可以理解为保存在磁盘上的包含了指令和数据的一个文件。

进程它是动态的,是操作系统的概念,是操作系统中程序执行和资源管理的最小单位“进程是一个程序的一次执行的过程”:每当我们执行一个程序时,对于操作系统来讲就创建了一个进程作为调度的基本单元,这个过程包括包括创建、调度和消亡;在这个过程中,伴随着资源的分配和释放。比如,linux的vi编辑器,它就是一段在linux下用于文本编辑的工具,那么它是一个程序,而我们在linux终端中,可以分别开启两个vi编辑器的进程。

参考

http://wiki.linuxdeepin.com/index.php?title=%E7%A8%8B%E5%BA%8F%E4%B8%8E%E8%BF%9B%E7%A8%8B

简单过一下ppt上的内容后重点提一下进程来自程序但又不同于程序。引出后面要重点讲解的进程和程序的差别。

1.2进程与程序(P6)

针对p6的这个图:

1.2.1 先讲一下程序

对程序的正文段和数据段的解释从elf格式上去理解。

程序可以理解为存放在某种存储介质上(一般可以理解为硬盘/flash等)的二进制字节码,在Linux上一般为ELF格式。

ELF格式(http://en.wikipedia.org/wiki/Executable_and_Linkable_Format)Executable and Linkable Format.

Linux系统上的可执行文件,.o文件,共享库,coredump文件都是ELF格式

给学生画个ELF格式的大致图理解一下。或者再用readelf 命令演示一下。

我们可以大致看到程序文件除了文件头之外,一般由多个段section组成,主要有代码段(text)和数据段(data)。

举例:<<<<<<主要用readelf看一下

可以通过objdump 或者readelf命令进行读取分析。

readelf h filename // 查看文件头

readelf a filename // 查看所有内容

objdump和readelf的使用区别:

http://blog.csdn.net/shenyan008/article/details/6889373

objdump和readelf都可以用来查看二进制文件的一些内部信息. 区别在于objdump借助BFD而更加通用一些, 可以应付不同文件格式, readelf则并不借助BFD, 而是直接读取ELF格式文件的信息, 按readelf手册页上所说, 得到的信息也略细致一些。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

1.2.2 再讲一下进程 (P7)

进程是一个程序被OS加载到RAM中执行后的一个完整的执行环境。

举例:<<< misc\ program_process.pptx >>>

参考http://oss.org.cn/kernel-book/ch04/4.1.htm

1)正文段(text):存放被执行的机器指令。这个段是只读的(所以,在这里不能写自己能修改的代码),它允许系统中正在运行的两个或多个进程之间能够共享这一代码。例如,有几个用户都在使用文本编辑器,在内存中仅需要该程序指令的一个副本,他们全都共享这一副本。

2)用户数据段(user segment):存放进程在执行时直接进行操作的所有数据,包括进程使用的全部变量在内。显然,这里包含的信息可以被改变。虽然进程之间可以共享正文段,但是每个进程需要有它自己的专用用户数据段。例如同时编辑文本的用户,虽然运行着同样的程序__编辑器,但是每个用户都有不同的数据:正在编辑的文本。

3)系统数据段(system segment):(课件上写的堆栈段是错误的)不要理解为ELF中的段,而应该理解为Linux为每个进程建立了task_struct数据结构来容纳这些控制信息。该段有效地存放程序运行的环境。事实上,这正是程序和进程的区别所在。如前所述,程序是由一组指令和数据组成的静态事物,它们是进程最初使用的正文段和用户数据段。作为动态事物,进程是正文段、用户数据段和系统数据段的信息的交叉综合体,其中系统数据段是进程实体最重要的一部分,之所以说它有效地存放程序运行的环境,是因为这一部分存放有进程的控制信息。系统中有许多进程,操作系统要管理它们、调度它们运行,就是通过这些控制信息。

1.2.3 task_struct:

内核会创建一个叫进程描述符task_struct的数据结构对象来管理该对象。

可以提示让学生思考并列出,打开文件演示一下几个关键的成员给大家演示一下linux/sched.h

state

进程的运行状态。参考sched.h中进程状态的宏定义。

rt_priority;

表示此进程的运行优先级

struct   mm_struct *mm;

该结构体记录了进程内存使用的相关情况,虚拟地址的概念可以提一下

pid_t   pid;

进程号,是进程的唯一标识

pid_t  tgid;

线程组id号,Each thread belongs to a group and the PID of the first thread (also known as the group leader) created in a thread group is stored in a field called tgid (thread-group id)

http://careers.directi.com/display/tu/Understanding+Processes+in+Linux

struct  task_struct  *real_parent;

real_parent是该进程的"亲生父亲",不管其是否被"寄养"

struct   task_struct  *parent;

parent是该进程现在的父进程,有可能是"继父"

struct   list_head    children;

这里children指的是该进程孩子的链表,可以得到所有孩子的进程描述符

struct    list_head    sibling;

同理,sibling该进程兄弟的链表,也就是其父进程的所有孩子的链表

struct   task_struct  *group_leader;

这个是主线程的进程描述符,也许你会奇怪,为什么线程用进程描叙符表示,因为linux并没有单独实现线程的相关结构体,只用一            个进程来代替线程,然后对其做一些特殊的处理。

struct   list_head   thread_group;

这个是该进程所有线程的链表 – 结合pid和tpid提一下线程和线程组的概念,后面讲到线程时会再展开讲

struct fs_struct *fs;

它包含此进程当前工作目录和根目录、

 

struct  files_struct  *files;

打开的文件相关信息结构体。f_mode字段描述该文件是以什么模式创建的:只读、读写、还是只写。f_pos保存文件中下一个读或写将发生的位置

struct   signal_struct  *signal;

struct   sigband_struct  *sighand;

信号相关信息的句柄

task_struct结构体非常庞大,我们没必要去了解它的所有字段,只需要对其中比较重要的字段加以关注就可以了。从上面的分析可以看出,一个进程至少有一下东东

1、 进程号(pid),就像我们的身份证ID一样,每个人的都不一样。进程ID也是,是其唯一标示。

2、 进程的状态,标识进程是处于运行态,等待态,停止态,还是死亡态

3、 进程的优先级和时间片。用于OS的进程调度.不同有优先的进程,被调度运行的次序不一样,一般是高优先级的进程先运行。时间片标识一个进程将被处理器运行的时间

4、 处理器相关上下文: 一个进程可以被认为是系统当前状态的总和。每当一个进程运行时,它要使用处理器的(PC,各种寄存器,堆栈指针,堆指针等),这是进程的上下文(context)。并且,每当一个进程被暂停时,所有的CPU相关上下文必须保存在该进程的task_struct中。当进程被调度器重新启动时其上下文将从这里恢复。

5、 进程运行时使用的其他资源,比如虚拟内存,Linux必须跟踪内存如何映射到系统物理内存。打开的文件, 未决的信号。

6、 甚至还有进程里的一个或者多个执行线程等等信息。

总结:所以我们可以认为进程这个对象就是CPU上正在执行的程序代码的实时结果(快照),内核需要将这些信息组织起来作为程序执行的上下文环境信息进行有效的管理。

进程的存储器安排。

1.3 Linux系统中的进程类型(P8)

参考:http://wiki.linuxdeepin.com/index.php?title=%E7%A8%8B%E5%BA%8F%E4%B8%8E%E8%BF%9B%E7%A8%8B

交互进程是由一个Shell启动的进程。交互进程:既可以在前台运行,也可以在后台运行。

批处理进程: 该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。

注:交互进程和批处理进程的概念主要是从进程调度的角度去分类的。参考:

总结:交互进程和批处理进程的概念我们并不需要特别地关注,需要知道的是交互型进程主要与外界交互,响应度要求较高,所以在进程优先级上需要高一些,而批处理类型的进程则相反。一般开发人员只有确实需要在实际需要自己手动调整系统时才会关注。了解一下就好了。

系统守护进程: 该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。后面会专门介绍

1.4 进程运行状态(P9~P10)

举例 <<<<<<< 先看看进程的状态。

1)运行top命令演示一下,注意看 第二行:Tasks: 153 total,   1 running, 152 sleeping,   0 stopped,   0 zombie

“S”那一列显示进程的状态变换

Ps命令也可以 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

状态

描述

R (TASK_RUNNING),可执行状态。

l R包括了教材上显示的:

² “运行态”就是R的就绪状态,进程获得了除CPU以外的所有资源

² “拥有CPU”就是R的正在执行状态。

l 对应R状态,这些进程的task_struct结构(进程控制块)被放入对应CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。进程调度器的任务就是一旦一个“拥有CPU”的进程时间片用完, Linux 内核的调度器会剥夺这个进程对这个CPU的控制权,并且从该CPU的可执行队列中选择一个合适的进程投入运行(通过schedule())。所以重点是要知道处于R状态的进程并不一定在CPU上执行。

很多操作系统教科书将正在CPU上执行的进程定义为RUNNING状态、而将可执行但是尚未被调度执行的进程定义为READY状态,这两种状态在linux下统一为 TASK_RUNNING状态。

S (TASK_INTERRUPTIBLE),可中断的睡眠状态。

有时候,进程需要等待直到某个特定的事件发生,例如设备初始化完成、I/O 操作完成或定时器到时等。在这种情况下,进程task_struct结构必须从可执行队列移出,加入到一个等待队列中,这个时候进程就进入了睡眠状态。所以所谓的睡眠就是进程从可执行队列被移到另一个等待队列中。

可中断的睡眠状态的进程会睡眠直到某个条件变为真,比如说产生一个硬件中断、释放进程正在等待的系统资源或是传递一个信号都可以是唤醒进程的条件。进程被唤醒后又进入R状态并被放到可执行队列中等待被调度到CPU上执行。注:这里的可中断不仅仅是指被信号中断。不要被后面TASK_UNINTERRUPTIBLE的不可被信号中断所迷惑。

通过ps -aux命令我们会看到,一般情况下,进程列表中的绝大多数进程都处于TASK_INTERRUPTIBLE状态(除非机器的负载很高)。毕竟CPU就这么一两个,进程动辄几十上百个,如果不是绝大多数进程都在睡眠,CPU又怎么响应得过来。

有关睡眠参考:http://blog.csdn.net/unbutun/article/details/5730089

D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态。

l 与TASK_INTERRUPTIBLE状态类似,进程处于睡眠状态,但是此刻进程是不可中断的。不可中断,指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,kill -9竟然杀不死一个正在睡眠的进程了!于是我们也很好理解,为什么ps命令看到的进程几乎不会出现TASK_UNINTERRUPTIBLE状态,而总是TASK_INTERRUPTIBLE状态。

TASK_UNINTERRUPTIBLE状态存在的意义就在于,内核的某些处理流程是不能被信号打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。(参见《linux内核异步中断浅析》)

在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用TASK_UNINTERRUPTIBLE状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的TASK_UNINTERRUPTIBLE状态总是非常短暂的,通过ps命令基本上不可能捕捉到。

以下例子暂时不演示!!!

linux系统中也存在容易捕捉的TASK_UNINTERRUPTIBLE状态。执行vfork系统调用后,父进程将进入TASK_UNINTERRUPTIBLE状态,直到子进程调用exit或exec(参见《神奇的vfork》)。

通过下面的代码就能得到处于TASK_UNINTERRUPTIBLE状态的进程:

#include   void main() {  if (!vfork()) sleep(100);  }

编译运行,然后ps一下:

kouu@kouu-one:~/test$ ps -ax | grep a\.out  4371 pts/0    D+     0:00 ./a.out  4372 pts/0    S+     0:00 ./a.out  4374 pts/1    S+     0:00 grep a.out

然后我们可以试验一下TASK_UNINTERRUPTIBLE状态的威力。不管kill还是kill -9,这个TASK_UNINTERRUPTIBLE状态的父进程依然屹立不倒。

T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。

对于进程本身来说,TASK_STOPPED和TASK_TRACED状态很类似,都是表示进程暂停下来。

l 何时进入TASK_STOPPED:向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程本身处于TASK_UNINTERRUPTIBLE状态而不响应信号)。(SIGSTOP与SIGKILL信号一样,是非常强制的。不允许用户进程通过signal系列的系统调用重新设置对应的信号处理函数。)向进程发送一个SIGCONT信号,可以让其从TASK_STOPPED状态恢复到TASK_RUNNING状态。

l 何时进入TASK_TRACED:当进程正在被跟踪时,它处于TASK_TRACED这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在gdb中对被跟踪的进程下一个断点,进程在断点处停下来的时候就处于TASK_TRACED状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。

两者的区别:TASK_TRACED状态相当于在TASK_STOPPED之上多了一层保护,处于TASK_TRACED状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程通过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操作(通过ptrace系统调用的参数指定操作),或调试进程退出,被调试的进程才能恢复TASK_RUNNING状态。

Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。

l 什么叫僵尸:进程在退出的过程中,处于TASK_DEAD状态。在这个退出过程中,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外。于是进程就只剩下task_struct这么个空壳,但这个空壳上至少记录了这个进程退出时的原因status-退出码以及一些统计信息,而linux内核认为这个退出码很重要,如果子进程结束后没有向父进程报告这个退出码或者说父进程没有通过回收来获取这个退出码都是不可接受的,它会一直保留这个空壳。这个task_struct的空壳就形象地被称为僵尸(a process that is dead already but does not disappear yet because it has not yet reported its exit status.)。 (当然,内核也可以将这些信息保存在别的地方,而将task_struct结构释放掉,以节省一些空间。但是使用task_struct结构更为方便,因为在内核中已经建立了从pid到task_struct查找关系,还有进程间的父子关系。释放掉task_struct,则需要建立一些新的数据结构,以便让父进程找到它的子进程的退出信息。)

l 如何清除僵尸:有如下三种方法:

² 子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来“收尸”。这个信号默认是SIGCHLD。如果父进程通过等待他(调用wait / waitpid)回收了这个僵尸进程,那么内核就会立即释放掉task_struct,即僵尸很快就被清理了。回收的方式在wait部分我们后面介绍。

² 父进程在创建子进程之前通知内核不想等待回收他,则内核在子进程结束后内核直接将子进程的僵尸回收掉。通知涉及信号,在信号处理时再讲。

² 如果子进程的父进程在子进程没有结束之前先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……。)而init成为子进程的继父进程后就会在其结束时负责清理它。(linux系统启动后,第一个被创建的用户态进程就是init进程。它有两项使命:1、执行系统初始化脚本,创建一系列的进程(它们都是init进程的子孙);2、在一个死循环中等待其子进程的退出事件,并调用waitid系统调用来完成“收尸”工作;init进程不会被暂停、也不会被杀死(这是由内核来保证的)。它在等待子进程退出的过程中处于TASK_INTERRUPTIBLE状态,“收尸”过程中则处于TASK_RUNNING状态。)

l 僵尸为何会长期存在:如果以上三种情况都未发生, 那么子进程的僵尸就会一直存在(即task_struct这个结构就一直占这内核的内存)。

举例:制造一个EXIT_ZOMBIE状态的进程:<<<<<<<<

samples\1.ProcessI\1.4-task_status\ task_Z.c

演示重点:

1) 如何通过ps或者top命令查看僵尸进程: 编译运行,然后ps一下:ps -ax | grep a.out

2) 演示清除僵尸进程的第三种方法(其他两种方法涉及wait和signal,都还没有讲),即杀死父进程,因为父进程不退出,这个僵尸状态的子进程就一直存在。>>>>>>

l 僵尸出现后我们还有其他方法杀死它们吗:同学可能会问,是否可以用kill来直接杀死僵尸呢?回答是不能,除非你舍得杀死parent。利用的原理就是让父进程先死,然后子进程就被init继养,然后就会被init回收的原理。所以我们写程序时要避免出现僵尸进程,否则浪费资源。(A zombie is already dead, so you cannot kill it. To clean up a zombie, it must be waited on by its parent, so killing the parent should work to eliminate the zombie. (After the parent dies, the zombie will be inherited by init, which will wait on it and clear its entry in the process table.) If your daemon is spwaning children that become zombies, you have a bug. Your daemon should notice when its children die and wait on them to determine their exit status.)

X (TASK_DEAD - EXIT_DEAD),退出状态,进程即将被销毁。

这个状态没有在我们的教材中显示,不讲了

而进程在退出过程中也可能不会保留它的task_struct。比如这个进程是多线程程序中被detach过的进程(进程?线程?参见《linux线程浅析》)。或者父进程通过设置SIGCHLD信号的handler为SIG_IGN,显式的忽略了SIGCHLD信号。(这是posix的规定,尽管子进程的退出信号可以被设置为SIGCHLD以外的其他信号。)

此时,进程将被置于EXIT_DEAD退出状态,这意味着接下来的代码立即就会将该进程彻底释放。所以EXIT_DEAD状态是非常短暂的,几乎不可能通过ps命令捕捉到。

参考:

1. Linux进程状态解析之R、S、D、T、Z、X

http://blog.csdn.net/nilxin/article/details/7437671

2. Linux进程的状态转换图

http://blog.csdn.net/mu0206mu/article/details/7348618

1.5 进程的模式(P11~P12)

http://stackoverflow.com/questions/2479118/cpu-switches-from-user-mode-to-kernel-mode-what-exactly-does-it-do-how-does-i

进程的执行模式分为用户模式和内核模式。实际上严格说应该是CPU的运行模式,要么处于受信任的内核模式,要么处于受限制的用户模式。内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。如果是运行在用户模式则受限,如果一个程序需要拥有内核模式权限,必须通过系统调用切换到内核模式执行系统功能,操作系统通过这种方式来确保系统的安全和稳定。进程的执行代码路径中通过调用一个系统调用以异常处理的方式陷入内核空间。

用户模式的代码允许发生缺页,而内核模式的代码则不允许。

2.4和更早的内核中,仅仅用户模式的进程可以被上下文切换出局,由其他进程抢占。除非发生以下两种情况,否则内核模式代码可以一直独占CPU:(1) 它自愿放弃CPU;(2) 发生中断或异常。2.6内核引入了内核抢占,大多数内核模式的代码也可以被抢占。

注:中断和异常的区别:http://bbs.csdn.net/topics/60216500。8086/8088把中断分为内部中断和外部中断两大类。为了支持多任务和虚拟存储器等功能,80386把外部中断称为“中断”,把内部中断称为“异常”。与8086/8088一样,80386通常在两条指令之间响应中断或异常。80386最多处理256种中断或异常。

2.进程相关操作命令

重点:

前台运行和后台运行的概念以及前后台切换的命令

周期运行的命令

查看进程的命令

指定进程运行优先级的命令

结束进程的命令

2.1 Linux下的进程管理(P13)

如何后台运行: &

 

linux系统如果你想要让自己设计的备份程序可以自动在某个时间点开始在系统底下运行,而不需要手动来启动它,又该如何处置呢? 这些例行的工作可能又分为一次性定时工作与循环定时工作,在系统内又是哪些服务在负责? 还有,如果你想要每年在老婆的生日前一天就发出一封信件提醒自己不要忘记,linux系统下该怎么做呢?

但是crontab 主要用来提交不断循环执行的job,  而at 用来提交一段时间后执行的job(执行完就自动删除整个job)

举例:<<<<

at命令

首先检查atd服务有无开启

在一个指定的时间执行一个指定任务,只能执行一次,且需要开启atd进程(

ps -ef | grep atd查看, 

开启用/etc/init.d/atd start or restart; 

开机即启动则需要运行 chkconfig --level 2345 atd on)

演示,定时在11:30am用ls列出当前目录内容并写入~/log文件

cd ~

at 11:30am today

at>ls > ~/t.log

at> <EOT> //按Ctl-D退出

cron命令

ps -ef | grep cron

参考http://88263188.blog.51cto.com/416252/291683

crontab -u //设定某个用户的cron服务,一般root用户在执行这个命令的时候需要此参数

crontab -l //列出某个用户cron服务的详细内容

crontab -r //删除某个用户的cron服务

crontab -e //编辑某个用户的cron服务

root查看自己的cron设置:crontab -u root -l

或者直接看自己名下的任务: crontab -l

创建任务:crontab -e

打开默认编辑器编辑后保存退出即可

修改默认编辑器可以使用:select-editor

如果使用vi,编辑后wq保存退出即可

编辑基本格式 :

*  *  *  *  *  command

分 时 日 月 周 命令

1列表示分钟1~59 每分钟用*或者 */1表示

2列表示小时1~23(0表示0点)

3列表示日期1~31

4列表示月份1~12

5列标识号星期0~6(0表示星期天)

6列要运行的命令

如果写为*, 表示每X

如果想定义间隔,在X后加"/"和间隔的数字

每隔一分钟打印一下系统时间

*/1 * * * * date >> ~/t.log   //>> means append

>>>>

2.2 调度进程(P14)

对讲过的命令ps,top, kill 可以提问学生

2.2.1 ps

实验 <<<< 实验4.1, 学生自己熟悉一下

4.1 -不仅要熟悉ps命令方法,更重要的是可以了解Linux进程的组成。

ps命令是Process Status的缩写

ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令。

Man 1 ps

ps:查看系统中的进程,Linux中可以使用ps -aux查看所有进程。其中PID代表进程ID,TTY是该进程是由哪个控制台启动的,CMD则是命令。

如果想列出更详细的信息,则可使用命令:“ps -auxw”。参数w表示加宽显示的命令行,参数w可以写多次,通常最多写3次,表示加宽3次,这足以显示很长的命令行了。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

2.2.2 top

top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。下面详细介绍它的使用方法。

参考:linux top命令详解

http://blog.csdn.net/sanshiqiduer/article/details/1933625

2.2.3 kill

kill命令的工作原理是,向Linux系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核就可以对进程标识号指定的进程进行操作。比如在top命令中,我们看到系统运行许多进程,有时就需要使用kill中止某些进程来提高系统资源。

譬如:系统多个虚拟控制台的作用是当一个程序出错造成系统死锁时,可以切换到其它虚拟控制台工作关闭这个程序。此时使用的命令就是kill,因为kill是大多数Shell内部命令可以直接调用的。

Kill –l // 显示所有kill可以发送的信号id

不多举例了

2.2.4 nice和renice

nice是按照指定的优先级执行命令

renice更改正在运行的进程的优先级

系统中运行的每个进程都有一个优先级(亦称“nice 值”),其范围从 -20 (最高优先级)到 19 (最低优先级)。优先级越高,其值越小。默认情况下,进程的优先级是 0 (“基本”调度优先级)。优先级比较大的进程(nice 值比较小,最低到 -20)相对优先级比较小的进程(直到 19)将比较频繁地被调度运行,因此就拥有更多的进程周期。一般用户只能降低它们自己进程的优先级别,并限于 0 到 19 之间。超级用户(root)可以将任何进程的优先级设定为任何值。

举例:<<<<

ps -l -p 2924

F S   UID   PID  PPID  C  PRI  NI   ADDR SZ  WCHAN  TTY          TIME CMD

0 S  1000  2924     1  0  80  0    -    8854 poll_s     ?        00:00:01 notify-osd

PRI指的是内核调度优先级
NICE是进程优先级

top里的nice,ps -efl里的NI

renice +15 785 //加值,降低优先级

nice -n 19 dd if=/dev/cdrom of=~/mdk1.iso

>>>>>

2.2.5 bg,fg

不能光讲这两个,要结合其他几个命令一块讲

jobs:查看当前有多少在后台运行的命令

&:运行一个程序时,直接放到后台

Ctrl-Z:将一个正在前台执行的进程放到后台,并且暂停

fg:将后台中的命令调至前台继续运行。如果后台中有多个命令,可以用 fg N将选中的命令调出,N是通过jobs命令查到的后台正在执行的命令的序号[N] (不是pid)。也可以用%代替fg。

bg:将一个在后台暂停的命令,变成继续执行。如果后台中有多个命令,可以用bg N将选中的命令调出,N是通过jobs命令查到的后台正在执行的命令的序号(不是pid)

举例:<<<<<

直接在后台开始运行一个命令

[maple@linux ~]$  ping baidu.com -a >/dev/null &

[1] 12879

显示后台命令运行状态

[maple@linux ~]$ jobs

[1]+  Running                 ping baidu.com -a > /dev/null &

将前台运行进程挂起放到后台

[maple@linux ~]$ ping google.com -a >/dev/null

#Ctrl+Z

[2]+  Stopped                 ping google.com -a > /dev/null

[maple@linux ~]$ jobs

[1]-  Running                 ping baidu.com -a > /dev/null &

[2]+  Stopped                 ping google.com -a > /dev/null

注意没有直接的命令可以将后台running的进程stop,必须要先把后台的进程通过fg调到前台,再用Ctrl+Z挂起后重新放回后台

[maple@linux ~]$ fg 1

ping baidu.com -a > /dev/null

#Ctrl+Z

[1]+  Stopped                 ping baidu.com -a > /dev/null

[maple@linux ~]$ jobs

[1]+  Stopped                 ping baidu.com -a > /dev/null

[2]-  Stopped                 ping google.com -a > /dev/null

通过bg可以直接将后台挂起的进程恢复执行

[maple@linux ~]$ bg 2

[2]- ping google.com -a > /dev/null &

[maple@linux ~]$ jobs

[1]+  Stopped                 ping baidu.com -a > /dev/null

[2]-  Running                 ping google.com -a > /dev/null &

>>>>>

提高:(课上估计没有太多时间讲。让学生自己回去看)

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

实验4.2:使用proc文件系统查看进程信息

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

3. 进程系统调用

重点:

Fork函数的机制

Exec函数形式的区别

Exit和_exit的区别

Wait的作用,wait和waitpid的区别,如何避免僵尸进程

3.1概述:和进程相关的系统调用

Fork;Exec;Exit/_exit;Wait/waitpid

3.2进程标识(这个不讲了

和一个进程相关的最重要的有6个标识。和IO部分-文件与目录章节的文件存取许可检查相关的四个ID,授课时可以简单回顾一下。

pid_t getpid(void); Returns: process ID of calling process

pid_t getppid(void);Returns: parent process ID of calling process

uid_t getuid(void);Returns: real user ID of calling process

uid_t geteuid(void);Returns: effective user ID of calling process

gid_t getgid(void);Returns: real group ID of calling process

gid_t getegid(void);Returns: effective group ID of calling process

3.3 Fork

综述:Unix的进程创建很特别。其他的操作系统都提供了一步产生spawn进程的机制,比如Windows里的CreateProcess。但Unix采用了与众不同的实现方式,它把上述步骤分解到两个单独的函数中去执行:fork和exec。一个现有进程通过调用Fork拷贝当前的进程创建新进程(由fork创建的新进程被称为子进程(child process))。子进程与父进程的区别仅仅在于PID,PPID和某些资源&统计量(例如,挂起的信号)。然后由exec函数负责读取可执行文件并将其载入地址空间开始运行。

3.3.1 fork函数说明(P16~P18)

3.3.1.1 如何理解fork被调用一次,但返回两次这个概念。

提示:用叉子的概念来引导。先对着P16讲一下函数,再对照P17讲下面的解释。

在分叉点上发生了什么事情:内核为我们拷贝创建了一个新的task_struct结构。子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间“副本”,这意味着父子进程间不共享这些存储空间。

由于在复制时复制了父进程堆栈段,所以两个进程都停留在fork函数中,等待返回。因此fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值是不一样的。过程可以参考一个图。回顾原来讲过的程序和进程中,进程的组成:代码段,数据段,内存空间的概念。文本段共享。函数返回时pid所在栈段被复制并填写了不同的返回值。

fork创建的新进程我们称之为子进程。fork被调用一次,但返回两次。唯一的区别是在子进程里返回值是0,而在父进程里其返回值是子进程的进程ID。

举例 <<< 课件P17Fork的例子:samples\1.ProcessI\3.3-fork\fork1.c  

演示过程:misc/fork.pptx

>>>>>>

举例:<<<< 验证fork后子进程持有的是上述存储空间“副本”,这意味着父子进程间不共享这些存储空间。

samples\1.ProcessI\3.3-fork \ fork2.c

演示过程:misc\fork-sample.pptx

这个例子讲解完后让学生自己运行体验一下fork的含义。

演示时有两点需要说明:

1) 从上面可以看出,因为子进程和父进程拥有独立的物理内存空间,所以当子进程对拷贝来的数据做修改的时候,并没有影响到父进程。

2) 复制的前后,其值的地址都是一样的。为什么呢?子进程拷贝的时候也拷贝了父进程的虚拟内存"页",这样他们的虚拟地址都一样,但是对应不同的物理内存空间。示例代码没有打印出物理地址,所以无法生动地展现这个现象,打印的只是虚拟地址。因为物理地址不好直接从用户态访问,除非我们在内核写驱动帮助用户态读取。http://www.it165.net/os/html/201205/2243.html

注:这个例子仿照了实验4.3,因为我觉得实验手册上的例子不够好,重新改了一个(出于简化考虑实验4.3(不准备讲解了)同时也没有仿照网上的例子演示标准IO的缓存对fork的影响。http://blog.chinaunix.net/uid-26833883-id-3222794.html)

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

3.3.1.2 使用fork函数的注意点:

一个现存的进程调用fork是unix内核创建一个新进程的唯一方法(除了内核自举过程中的进程创建,比如init等)

注意在父进程里保存好其获得的子进程的ID,因为我们没有其他的系统调用可以获得一个父进程的子进程ID号。而对于子进程来说却可以通过getppid来获取其父进程的ID。进程ID已经被内核保留,所以在用户态我们可以使用0来判断。

我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。

3.3.2 fork的优化历史- P19

下面采用历史发展的叙述方式给学生介绍一下fork的优化历史,是如何一步一步提高效率的。引入COW(Linux上fork)和vfork。对于vfork,根据LKD p28的说法,理想情况下,最好不要调用vfork。所以培训中简单介绍一下vfork就可以了。重点还是讲fork。

主要可以参考:Linux写时拷贝技术(copy-on-write)

演示:misc\fork-cow.pptx

http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

1) 经典的fork实现

复制地址空间 – 进程的页表(图中的从左到右的箭头)共享正文段;其他段立即复制一份

 

2)支持写时复制的(Copy-On-Write COW技术)的fork实现(目前Linux的实现方式)

传统的fork直接把所有资源复制给新建的子进程,这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的程序,那么所有的拷贝工作都将前功尽弃。

内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下—举例来说,fork()后立即调用exec()—它们就无需复制了。fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。由于Unix强调进程快速执行的能力,所以这个优化是很重要的。这里补充一点:Linux COW与exec没有必然联系

 

注:最新的Linux的fork状态还是看LKD吧

提高<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

3)Vfork

vfork():这个做法更加火爆,内核连子进程的虚拟地址空间结构也不创建了,直接共享了父进程的虚拟空间,当然了,这种做法就顺水推舟的共享了父进程的物理空间。(其行为在vfork的初期P2已经非常类似于被创建成了和P1在同一个进程中的线程了,因为他们完全共享了虚拟空间)

注,因为Linux上并不推荐使用vfork,所以课上不再展开讲。原因:vfork的好处有限,仅限于不拷贝页表项,而且vfork的语义比较微妙,使用时对父进程的进一步动作有依赖(共享物理空间所致),所以没有特别需要,并不建议多使用vfork。

 

以下参考有点偏理论化,可以参考但和最新的LKD对Linux的描述稍有出入:

1) Linux多进程开发(二)进程创建之vfork的学习:

http://liam2199.blog.51cto.com/2879872/1231680

2) fork与vfork的区别

http://blog.csdn.net/jianchi88/article/details/6985326

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

3.3.3 实验

<<< 实验手册4.3, 练习fork >>>

3.4 exec函数族

3.4.1基本介绍(P20~P21)

fork函数用于创建一个子进程,该子进程几乎拷贝了父进程的全部内容。

exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。

可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

每当进程调用一种exec函数时,该进程完全由新程序代换,而新程序从main函数开始执行。Exec并不创建新进程,所以前后进程ID也不会变。Exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。

3.4.2 exec函数族语法(P22-P23)

参考APUE2nd的8.10章节. 演示<<< misc/exec.pptx >>>

 

可执行文件查找方式

向新程序传递参数表的方式

向新程序传递环境表的方式

函数

路径名pathname

文件名filename

列表方式list

矢量数组方式vector

继承调用进程的environ变量

指定新的环境字符串指针数组envp[]

execl

*

 

*

 

*

 

execlp

 

*

*

 

*

 

execle

*

 

*

 

 

*

execv

*

 

 

*

*

 

execvp

 

*

 

*

*

 

execve

*

 

 

*

 

*

字母表示

 

p

l

v

 

e

返回值:注意如果exec执行成功是不会返回的,除非失败返回-1。

形参:

² 对文件查找方式为p的,

ü 如果filename中包含/,则将filename等同为pathname进行处理

ü 否则就查看PATH环境变量,在PATH中指定的目录下依次查找可执行文件

² 对传递参数为列表方式l的,注意列表包含argv0并且不要遗漏最后一个为NULL/0:arg0, arg1, … argn, NILL

 举例,<<<<< 见P24-P25samples\1.ProcessI\3.4-exec >>>>>>

3.4.3 实验

<<< 实验手册4.4, 基于手册4.3 做4.4的第一部分exec  >>>

3.5 exit/_exit

直接看ppt的图和例子就好

exit()函数与_exit()函数最大的区别就在于exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的清理I/O缓冲一项。

参考:exit()与_exit()在linux进程控制中的区别

http://blog.csdn.net/xiaozhi_su/article/details/4165174

举例<<<<<<<<samples\1.ProcessI\3.5-exit

Exit的例子,P29页例子,说明exit后面得代码不会被执行

P30页的例子: samples\1.ProcessI\3.5-exit\diff_exit

对于_exit后为何不会打印第二句话,请学生自己回答一下。

注意printf("This is the end"); 这句话最后没有\n, 所以该语句被调用后会缓存而不显示,_exit被调用后进程结束就没有机会显示了,而exit还会做一次类似fflush的操作。>>>>>>>>>>>>>>>>>>>>>>>>>

3.6 wait和waitpid

3.6.1引言:

回顾一下前面讲过的僵尸进程以及回收的问题

3.6.2 介绍wait, waitpid(P31-P33)

先说Wait:

1)他的主要作用是用来接收子进程的状态,退出或终止状态

2)对于我们一个程序来说,可能有多个子进程,那么接收哪个子进程的状态,是不确定的

3)wait函数会暂停我们的调用进程,直到子进程的终止。(收到子进程的退出信号)

再说waitpid

1) pid不深入讲涉及进程组的概念,因为这里还不回讲到进程组。所以重点讲pid>和pid=-1的情况。当第一个参数pid为-1时,就是wait的功能,等待任一子进程退出,还可以指定特定的pid

2) Option选项比较特殊,我们可以用两个常规选项,0与WNOHANG,0的作用与wait一样,会阻塞,而WNOHANG不会暂停我们的调用进程,根据子进程是否退出或暂停,如果退出就返回0,如果正确返回就返回子进程号。还有一个WUNTRACED,这个是用来查看子进程自暂停或退出以来还未报告,就返回其状态。对任务支持不是很好,这个不常用。

Wait和waitpid的返回状态码:

Man 2 wait 使用系统定义的宏对wait的status进行判断和读取,不要直接打印。带领学生看man手册。注意WIFEXITEDWEXITSTATUS

举例:<<<<< 利用wait回收子进程的僵尸

回顾消除僵尸的正确方式:1)父进程提前退出;2)真父委托继父;3)真父wait。1已经前面演示过,2涉及signal等到后面演示,这里主要演示3。比较wait后和寄养后僵尸不出现

samples\1.ProcessI\3.6-wait\wait_z.c: 制造僵尸

samples\1.ProcessI\3.6-wait\wait.c: 使用wait回收

samples\1.ProcessI\3.6-wait\waitpid.c,使用waitpid回收

简单说一下对于由真父调用wait, waitpid来接收子进程退出状态。达到收尸的作用。但简单使用该方法需要父进程等待子进程结束,会导致父进程挂起。特别是在某些服务器实现上,一般由父进程fork很多子进程,如果需要父进程去长时间等待子进程,会很影响服务器进程的并发性能。为了避免父进程长时间等待还有一种方法,但要涉及signal函数的处理,留到后面signal的时候再讲>>>>>>>>>

3.6.3 实验

<<<< 课件P34大家看图写程序,我们先来分析一下,我们这张图呢是有两上进程,子进程中暂停5秒,父进程轮循环检测子进程的退出状态,直到进程退出才返回。

也可以让学生用阻塞的方式,即wait来实现。阻塞与非阻塞的轮循的概念:

阻塞:当进程条件得不到满足时,系统会调用schedule()将进程挂起,当我们条件满足调用wake唤醒阻塞进程

非阻塞:当条件不满足,会立即返回执行下面的语句。

轮询:对于咱们的程序来说,阻塞与非阻塞都不能满足咱们的要求,咱们可以得用循环的机制与非阻塞的机制配合,这种方法叫做轮循。>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

<<< 有时间做一下实验手册4.4的第二部分 >>>

4. Linux守护进程

重点:

了解守护进程的特点

了解ps中查看守护进程的方法

了解会话相关概念

掌握编写守护进程的方法

4.1 基本概念:

先来复习一下,进程的类型,进程大体分为三类,交互进程,批处理进程,守护进程,

引入守护进程的基本概念:(可以参考的PPT)守护线程的特点:

运行方式: 守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。Linux系统有很多守护进程,大多数服务都是用守护进程实现的。比如:像我们的tftp,samba,nfs等相关服务。UNIX的守护进程一般都命名为*d的形式,如httpd,telnetd等等

生命周期: 守护进程会长时间运行,常常在系统启动时就开始运行,直到系统关闭时才终止

守护进程不依赖于终端:显而异见,从终端开始运行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。咱们平常写进程时,一个死循环程序,咱们不知道有ctrl+c的时候,怎么关闭它呀,是不是关闭终端呀。也就是说关闭终端的同时也关闭了我们的程序,但是对于守护进程来说,其生命周期守护需要突破这种限制,它从开始运行,直到整个系统关闭才会退出,所以守护进程不能依赖于终端。 

4.2 查看系统中的守护进程

Ps –axj

-a: 显示所有

-x:显示没有控制终端的进程

-j:显示与作业有关的信息(显示的列):会话期ID(SID),进程组ID(PGID),控制终端(TT),终端进程组ID(TRGID)

如何识别一个守护进程:

所有的守护进程都是以超级用户启动的(UID为0);

没有控制终端(TTY为?);

终端进程组ID为-1(TPGID表示终端进程组ID,该值表示与控制终端相关的前台进程组,如果未和任何终端相关,其值为-1;

所有的守护进程的父进程都为init进程(PID为1的进程)。

举例:<<<<<<  看看守护进程的例子。找一个Xd的进程看看

2109  2110  1124  1124 ?     -1 S     1000   0:00 gnome-pty-helper //和终端无关的进程,TTY为?TPGID为?

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

对照ppt先介绍一下ps中title的含义

4.3会话相关概念:进程组、前台进程组、后台进程组、会话、控制终端。

参考:http://lesca.me/archives/process-relationship.html

http://www.cnblogs.com/hazir/p/linux_kernel_pid.html

www.cs.fsu.edu/~xyuan/cop5570/lect7_session.ppt

http://www.win.tue.nl/~aeb/linux/lk/lk-10.html

提前到这里先讲一下,结合参考课件P41的图。分

第一部分:进程组&会话

进程组:

² shell里的每个进程都属于一个进程组,创建进程组的目的是用于简化向组内所有进程发送信号的操作,即如果一个信号是发给一个进程组,则这个组内的所有进程都会受到该信号

² 进程组ID叫做PGID,进程组内的所有进程都有相同的PGID,等于该组组长的PID。进程组组长:进程组中有一个进程担当组长。进程组ID(PGID)等于进程组组长的进程ID。)已知一个进程,要得到该进程所属的进程组ID可以调用getpgrp。一个进程可以通过另一个系统调用setpgrp来加入一个已经存在的进程组或者创建一个新的进程组。

² 如果内核支持_POSIX_JOB_CONTROL(该宏被定义)则内核会为Shell 上的一条命令行(可能由多个命令通过管道等连接)创建一个进程组从这点上看,进程组不是进程的概念,而是shell上才有,所以在task_struct里并没有存储进程组id之类的变量。

² 进程组的生命周期到组中最后一个进程终止或加入其他进程组(离开本进程组)为止

会话:一般一个用户登录后新建一个会话,每个会话也有一个ID来标识(SID)。登录后的第一个进程叫做会话领头进程(session leader),通常是一个shell/bash。对于会话领头进程,其PID=SID。

举例:<<<<< 看看一个session的大致情况

proc1 | proc2 &

proc3 | proc4 | proc5

results in a session with three groups, see ‘ps –j’ 我们可以打开两个终端ps aj| grep <终端号>看一下另外一个终端里的情况。>>>>>

总结会话和进程组的关系:

² A session contains a number of process groups,

² A process group contains a number of processes

² A process contains a number of threads. 

² All these objects have numbers, and we have thread IDs, process IDs, process group IDs and session IDs.

第二部分:会话中的终端,前台&后台的概念

此处引入一个问题,Only one I/O device for all processes (and process groups) in a session. Which process should get the input from the keyboard?

控制终端:一个会话一般会拥有一个控制终端用于执行IO操作。会话的领头进程打开一个终端之后, 该终端就成为该会话的控制终端与控制终端建立连接的会话领头进程称为控制进程 (controlling process) 一个会话只能有一个控制终端

前台进程组 该进程组中的进程能够向终端设备进行读、写操作的进程组。例如登陆shell(例如bash)通过调用int tcsetpgrp(int fd, pid_t pgrp); 函数设置为某个进程组pgrp关联终端设备fd,该函数执行成功后,该进程组pgrp成为前台进程组。

后台进程组:该进程组中的进程只能够向终端设备

终端进程组ID:每个进程还有一个属性,终端进程组ID(TPGID),用来标识一个进程是否处于一个和终端相关的进程组中。前台进程组中的进程的TPGID=PGID,后台进程组的PGID≠TPGID。若该进程和任何终端无关,其值为-1。通过比较他们来判断一个进程是属于前台进程组,还是后台进程组。

总结:

² 每个会话有且只有一个前台进程组,但会有0个或者多个后台进程组。

² 产生在控制终端上的输入Input)和信号Signal)将发送给会话的前台进程组中的所有进程。对于输出(Output)来说,则是在前台和后台共享的,即前台和后台的打印输出都会显示在屏幕上。

² 终端上的连接断开时 (比如网络断开或 Modem 断开), 挂起信号将发送到控制进程(controlling process) 。

实例总结:课件P41页

 

一个用户登录后创建一个会话。一个会话中只存在一个前台进程组,但可以存在多个后台进程组。第一次登陆后第一个创建的进程是shell,也就是会话的领头进程,该领头进程缺省处于一个前台进程组中并打开一个控制终端可以进行数据的读写。当在shell里运行一行命令后(不带&)创建一个新的进程组,命令行中如果有多个命令会创建多个进程,这些进程都处于该新建进程组中,shell将该新建的进程组设置为前台进程组并将自己暂时设置为后台进程组。

举例 <<<<<<<<<<演示实例总结的图:

ping 127.0.0.1 -aq | grep icmp // 通过管道将两个命令串接起来ping –q不显示timeout信息

将其设置到后台并runing

采用类似方法,在前台再新建一个进程组。

开启另外一个终端并运行

Ps axj或者运行 ps axj | grep pts/0即过滤只看pts/0里的会话

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

2109  2111  2111  2111 pts/0     2538 Ss    1000   0:01 bash

 2111  2503  2503  2111 pts/0     2538 S     1000   0:00 ping 127.0.0.1 -aq

 2111  2504  2503  2111 pts/0     2538 S     1000   0:00 grep --color=auto icmp

 2111  2538  2538  2111 pts/0     2538 S+    1000   0:00 ping 127.0.0.2 -aq

 2111  2539  2538  2111 pts/0     2538 S+    1000   0:00 grep --color=auto timeo

SID都相同,说明大家都在一个Session里

有三个进程组 2111,2503 和2538。 我们可以看到用|连起来的ping和grep是在一个进程组里的。

2538这个进程组是一个前台的进程组,因为其PGID==TGPID, 2503这个进程组是一个后台进程组

在终端中执行Ctrl+C后

在第二个终端里继续ps axj | grep pts/0

PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

2109  2111  2111  2111 pts/0     2111 Ss+   1000   0:01 bash

 2111  2503  2503  2111 pts/0     2111 S     1000   0:00 ping 127.0.0.1 -aq

 2111  2504  2503  2111 pts/0     2111 S     1000   0:00 grep --color=auto icmp

2538那个前台进程组的所有进程都消失了,说明信号会发给前台进程组的所有进程

2111,即bash所在的那个进程组成为了前台进程组。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

4.4 Linux守护进程编写步骤

参考

1)华清远见blog:http://fasight001.spaces.eepw.com.cn/articles/article/item/99552

2)linux学习笔记-读《Linux编程技术详解》-守护进程http://blog.csdn.net/iron_lzn/article/details/5517776

先带领学生再回顾一下前述守护进程的特点:后台运行,不依赖于终端。

4.4.1 Step1创建子进程,父进程退出。P39)

实质是 init进程成为新产生进程的父进程。调用fork函数创建子进程后,使父进程立即退出。从而使产生的子进程将变成孤儿进程,并被init进程接管,同时,所产生的新进程将变为在后台运行;利用前面介绍的父进程先于子进程退出后内核会自动托付子进程给init的原理。

4.4.2 Step2在子进程中创建新会话。P40)

这个步骤是创建守护进程最重要的一步,虽然实现非常简单,但意义却非常重大。

 setsid()函数的作用。一个进程调用setsid()函数后,会发生如下事件:

² 首先内核会创建一个新的会话,并让该进程成为该会话的leader进程,

² 同时伴随该session的建立,一个新的进程组也会被创建,同时该进程成为该进程组的组长。

² 该进程此时还没有和任何控制终端关联。若需要则要另外调用tcsetpgrp,前面讲前台进程组时介绍过。

调用setsid()有以下3个作用:

让进程摆脱原会话的控制。

让进程摆脱原进程组的控制。

让进程摆脱原控制终端的控制。

那么,在创建守护进程时为什么要调用setsid()函数呢?读者可以回忆一下创建守护进程的第一步,在那里调用了fork()函数来创建子进程再令父进程退出。由于在调用fork()函数时,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出了,但原先的会话期、进程组和控制终端等并没有改变,因此,还不是真正意义上的独立。而setsid()函数能够使进程完全独立出来,从而脱离所有其他进程和终端的控制。

详细见man 2 setsid。

4.4.3 Step3 改变当前目录为根目录。

这一步也是必要的步骤。使用fork()创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统(如“/mnt/usb”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(如系统由于某种原因要进入单用户模式)。因此,通常的做法是让“/”作为守护进程的当前工作目录,这样就可以避免上述问题。当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如/tmp。改变工作目录的常见函数是chdir()。

4.4.4 Step4 重设文件权限掩码。

文件权限掩码是指屏蔽掉文件权限中的对应位。例如,有一个文件权限掩码是050,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用fork()函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为0,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是umask()。在这里,通常的使用方法为umask(0)。即赋予最大的能力。

回顾/介绍umask的用法

首先在一个普通文件创建时内核的文件系统有个限制:不允许你在创建一个普通文件时就赋予它执行权限,必须在创建后用chmod命令增加这一权限。目录则允许设置执行权限。所以一个文件被创建时的最大权限是666(110-110-110),目录被创建的最大权限是777(111-111-111)

掩码就是指出在此基础上用户可以设置哪些位在创建时也不允许使能。

假设umask(002)002即 000-000-010, 即不允许在创建时赋予other写权限,那么创建时

文件的权限就是110-110-100, 即664

目录的权限就是111-111-101, 即775

4.4.5 Step5 关闭文件描述符。

同文件权限掩码一样,用fork()函数新建的子进程会从父进程那里继承一些已经打开的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法被卸载。

在上面的第(2)步之后,守护进程已经与所属的控制终端失去了联系,因此,从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如printf())输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2的3个文件(常说的输入、输出和报错这3个文件)已经失去了存在的价值,也应被关闭。通常按如下方式关闭文件描述符:参考ppt

4.4.6 流程总结 

这样,一个简单的守护进程就建立起来了。创建守护进程的流程图如图2所示。课件上的图上的描述太简单,建议加上更多的解释才好,例如下图。


2 创建守护进程流程图

4.4.7 守护进程的出错处理(提高)

4.4.8 实验:

<<<< 不建议参考实验手册,先对照我的例子讲讲D:\work\courses\notes\2.2-Process\labs \4.5 

参考 Daemon程序的原理和实现http://blog.csdn.net/acs713/article/details/8696799 >>>>>>>>>

原创粉丝点击