进程管理 - 实践篇
1、 程序和进程
1.1 程序
程序( program )是存放在磁盘文件中的可执行文件。
1.2 进程和进程 ID
程序的执行实例被称为进程 (process) 。本书的每一页几乎都会使用这一术语。某些操作系统用任务表示正被执行的程序。
每个 linux 进程都一定有一个唯一的数字标识符,称为进程 ID ( process ID )。进程 ID 总是一非负整数。
1.3 linux 下的进程结构
Linux 系统是一个多进程的系统,进程之间具有并行性、互不干扰的特点。
linux 中进程包含 3 个段,分别为 “ 代码段 ” 、 “ 数据段 ” 和 “ 堆栈段 ” 。
“ 数据段 ” 存放全局变量、常数以及动态数据分配的空间( malloc 函数取得的空间);
“ 代码段 ” 存放程序代码;
“ 堆栈段 ” 存放子程序的返回地址、子程序的参数以及程序的局部变量。
2、 init 进程
进程 ID 为 1 通常是 init 进程,在自举过程结束时由内核调用。
init 进程绝不会终止。
它是一个普通的用户进程 ( 与交换进程不同,它不是内核中的系统进程 ) ,但是它以超级用户特权运行。注:驱动程序是在内核空间运行。
获取进程标识
n #include <sys/types.h>
n #include <unistd.h>
n pid_t getpid(void); 返回:调用进程的进程 I D
n pid_t getppid(void); 返回:调用进程的父进程 I D
n uid_t getuid(void); 返回:调用进程的实际用户 I D
n uid_t geteuid(void); 返回:调用进程的有效用户 I D
n gid_t getgid(void); 返回:调用进程的实际组 I D
n gid_t getegid(void); 返回:调用进程的有效组 I D
3 , fork 函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void); # 在已有的进程中创建新进程,复制所有信息
返回: >0 在父进程中; =0 在子进程中; -1 为出错
由 fork 创建的新进程被称为子进程( child process )。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是 0 ,而父进程的返回值则是子进程的进程 ID 。
一般来说,在 f o r k 之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
使用 fork 函数得到的子进程是父进程的处继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
父、子进程之间的区别是:
n fork 的返回值;
n 进程 I D 、不同的父进程 I D ;
n 子进程的 t m s _ u t i m e( 毫秒 ) , t m s _ s t i m e( 秒 ) , t m s _ c u t i m e 以及 t m s _ u s t i m e 设置为 0 ;
n 父进程设置的锁,子进程不继承;
n 子进程的未决告警被清除;
n 子进程的未决信号集设置为空集。
Fork 例如: fork.c
#
vfork 函数
vfork 函数的调用序列和返回值与 fork 相同,但两者的语义不同。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在 f o r k 之后经常跟随着 exec 。作为替代,使用了在写时复制 ( copy-on-Write, COW) 的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的 “ 页 ” ,做一个拷贝。如: uclinux 中的进程创建。
4 , exec 函数
在用 f o r k 函数创建子进程后,子进程往往要调用一种 e x e c 函数以执行另一个程序。
当进程调用一种 e x e c 函数时,该进程完全由新程序代换,而新程序则从其 m a i n 函数开始执行。因为调用 e x e c 并不创建新进程,所以前后的进程 I D 并未改变。 e x e c 只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。
#include <unistd.h>
int execl(const char * pathname , const char * arg 0 , ... /* (char *) 0 */); # list 字符串
int execv(const char * pathname , char *const a rgv [] ); # vector
int execle(const char * pathname , const char * a rg 0 , ... # environment
/* (char *)0, char *const e n v p [] */);
int execve(const char * pathname char *const a rgv [], char *const envp [] );
int execlp(const char * pathname , const char * a rg 0 , ... /* (char *) 0 */); # path
int execvp(const char * pathname , char *const a rgv [] );
六个函数返回:若出错则为 - 1 ,若成功则不返回
参数表的传递有关 ( l 表示表 ( list ) , v 表示矢量 ( vector ) ) ;
e :可传递新进程环境变量, execle 、 execve ;
p :可执行文件查找方式为文件名, execlp 、 execvp ;
例如: execlp.c, execl.c, execle.c, execve.c
#
5 , exit 和 _exit
exit 和 _exit 用于中止进程;
_exit 的作用:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的数据结构;
exit 与 _exit 函数不同, exit 函数在调用 exit 系统之前要检查文件打开情况把文件缓冲区的内容写回文件中去。如调用 printf ()函数。
6 , wait 和 waitpid 函数
当一个进程正常或异常终止时,内核就向其父进程发送 SIGCHLD 信号。因为子进程终止是个异步事件 ( 这可以在父进程运行的任何时候发生 ) ,所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数 ( 信号处理程序 ) 。对于这种信号的系统默认动作是忽略它。
wait 函数用于使父进程阻塞,直到一个子进程结束或者该进程接收到一个指定信号为止。
调用 wait 或 waitpid 的进程可能会:
n 阻塞 ( 如果其所有子进程都还在运行 ) 。
n 带子进程的终止状态立即返回 ( 如果一个子进程已终止,正等待父进程存取其终止状态 ) 。
n 出错立即返回 ( 如果它没有任何子进程 ) 。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int * status ) ;
pid_t waitpid(pid_t pid , int * status , int options ) ;
两个函数返回:若成功则为子进程 I D 号,若出错则为 -1.
Status 选项,为空时,代表任意状态结束的子进程,若不为空,则代表指定状态结束的子进程。
wait 和 waitpid 函数的区别:
n 在一个子进程终止前, wait 使其调用者阻塞,而 waitpid 有一选择项,可使调用者不阻塞。
n waitpid 并不等待第一个终止的子进程 — 它有若干个选择项,可以控制它所等待的特定进程。
n 实际上 wait 函数是 waitpid 函数的一个特例。
对于 waitpid 的 p i d 参数的解释与其值有关:
n pid == -1 等待任一子进程。于是在这一功能方面 waitpid 与 wait 等效。
n pid > 0 等待其进程 I D 与 p i d 相等的子进程。
n pid == 0 等待其组 I D 等于调用进程的组 I D 的任一子进程。
n pid < -1 等待其组 I D 等于 p i d 的绝对值的任一子进程。
waitpid 函数提供了 wait 函数没有提供的三个功能:
(1) waitpid 等待一个特定的进程 ( 而 w a i t 则返回任一终止子进程的状态 ) 。
(2) waitpid 提供了一个 w a i t 的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid 支持作业控制(以 WUNTRACED 选择项)。
例如: waitpid.c
守护进程
1 ,概述
守护进程( daemon )是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。 linux 系统有很多守护进程,它们执行日常事物活动。
2 ,守护进程特征
n 所有守护进程都以超级用户(用户 I D 为 0 )的优先权运行。
n 没有一个守护进程具有控制终端 — 终端名称设置为问号(?)、终端前台进程组 I D 设置为- 1 。缺少控制终端可能是精灵进程调用了 s e t s i d 的结果。
n 除 u p d a t e 以外的所有精灵进程都是进程组的首进程,对话期的首进程,而且是这些进程组和对话期中的唯一进程。 u p d a t e 是它所在进程组和对话期(中的唯一进程,但是该进程组的首进程(可能也是该对话期的首进程)已经终止。
n 所有这些守护进程的父进程都是 i n i t 进程。
3 ,守护进程编程规则 (5 步 )
( 1 )创建子进程,父进程退出:
首先做的是调用 fork ,然后使父进程 e x i t 。这样做实现了下面几点:第一,如果该守护进程是由一条简单 s h e l l 命令起动的,那么使父进程终止使得 s h e l l 认为这条命令已经执行完成。第二,子进程继承了父进程的进程组 I D ,但具有一个新的进程 I D ,这就保证了子进程不是一个进程组的首进程。这对于下面就要做的 setsid 调用是必要的前提条件。
( 2 )调用 setsid 以创建一个新的会话,并担任该会话组的组长。调用 setsid 作用有三个:
( a )成为新对话期的首进程,
( b )成为一个新进程组的首进程,
( c )脱离控制终端。
(会话组是一个或多个进程组的集合)
setsid ()函数格式:
# include <sys/types.h>
# include <unist.h>
Pid_t setsid(void)
函数成功时返回该进程组 ID, 出错时返回 -1
( 3 )改变当前目录为根目录
chdir(“/”);
从父进程继承过来的当前工作目录可能在一个 mnt 的文件系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个 mnt 文件系统中,那么该文件系统就不能被拆卸。
( 4 )重设文件权限掩码
umask(0);
由继承得来的文件方式创建屏蔽字可能会拒绝设置某些许可权。例如,若守护进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
(5) 关闭不再需要的文件描述符。
用 fork 函数创建的子程序会从父进程那继承一些已经打开的文件,由此为使守护进程就不再持有从其父进程继承来的某些文件描述符。但是,究竟关闭哪些描述符则与具体的精灵进程有关,可以程序中的方法关闭所有文件描述符。
for (i=0;i<MAXFILE;I++)
close ( i );
守护例如: dameon.c
4 ,守护进程的出错处理
由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员。
通常的办法是使用 syslog 服务,将出错信息输入到 “/var/log/message” 系统日志文件中去。
Syslog 是 linux 中的系统日志管理服务通过守护进程 syslog 来维护。
syslog 函数说明
Openlog 函数用于打开系统日志服务的一个连接;
Syslog 函数用于向日志文件中写入消息,在这里可以规定消息的优先级、消息的输出格式等;
Closelog 函数用于关闭系统日志服务的连接。
syslog 函数格式
( 1 ) openlog 函数
#include <syslog.h>
void openlog(char * ident , int option , int facility ) ;
Ident : 要向每个消息加入的字符串,通常为程序的名称;
option 参数:
n LOG_CONS : 若日志消息不能通过发送至 syslogd ,则将该消息写至控制台;
n LOG_NDELAY : 立即打开 UNIX 域数据报套接口至 syslsgd 守护进程。通常,在记录第一条消息之前,该套接口不打开。
n LOG_PERROR :除将日志消息发送给 syslog 外,还将它写至标准出错( stderr )。
n LOG_PID :每条消息都包含进程 ID ,此选择项可供对每个请求都 fork 一个子进程的守护进程使用。
openlog 的 facility 参数
n LOG_AUTH 授权程序 : login.su,getty, ⋯
n LOG_CRONcron 和 at
n LOG_DAEMON 系统守护进程: ftpd,routed, ⋯
n LOG_KERN 内核产生的消息
n LOG_LOCAL0 ~ 7 保留由本地使用
n LOG_LPR 行打系统: lpd, lpc, ⋯
n LOG_MAIL 邮件系统
n LOG_NEWSU senet 网络新闻系统
n LOG_SYSLOG syslogd 守护进程本身
n LOG_USER 来自其他用户进程的消息
n LOG_UUCP UUCP 系统
syslog 函数
# include <syslog.h>
void syslog(int priority , char *format , ...);
Priority 选项(消息优先级)
n LOG_EMERG 紧急 ( 系统不可使用 ) ( 最高优先级 )
n LOG_ALERT 必须立即修复的条件
n LOG_CRIT 临界条件 ( 例如,硬设备出错 )
n LOG_ERR 出错条件
n LOG_WARNING 警告条件
n LOG_NOTICE 正常,但重要的条件
n LOG_INFO 信息性消息
n LOG_DEBUG 调试排错消息 ( 最低优先级 )
closelog 函数
#include <syslog.h>
void closelog(void);
守护进程日志系统见例: syslog_dema.c