嵌入式系统学习9-Linux进程控制
时间:2009-03-08 来源:beauty2001
第 9 章 Linux进程控制
1、程序和进程
1.1 程序
程序(program)是存放在磁盘文件中的可执行文件。
1.2 进程和进程ID
程序的执行实例被称为进程(process)。本书的每一页几乎都会使用这一术语。某些操作
系统用任务表示正被执行的程序。每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一非负整数。
1.3 linux下的进程结构
Linux系统是一个多进程的系统,进程之间具有并行性、互不干扰的特点。
linux中进程包含3个段,分别为“代码段”、“数据段”和“堆栈段”。
代码段 数据段 堆栈段
“数据段”存放全局变量、常数以及动态数据分配的空间(malloc函数取得的空间);
“代码段”存放程序代码;
“堆栈段”存放子程序的返回地址、子程序的参数以及程序的局部变量。
1.3 init进程
进程ID为1通常是init进程,在自举过程结束时由内核调用。
init进程绝不会终止。
它是一个普通的用户进程(与交换进程不同,它不是内核中的系统进程),但是它以超级用户特权运行。
1.4 获取进程标识
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程I D
pid_t getppid(void); 返回:调用进程的父进程I D
uid_t getuid(void); 返回:调用进程的实际用户I D
uid_t geteuid(void); 返回:调用进程的有效用户I D
gid_t getgid(void); 返回:调用进程的实际组I D
gid_t getegid(void); 返回:调用进程的有效组I D
1.5 fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程I D,出错为-1
1.6 进程创建
由fork创建的新进程被称为子进程( childprocess)。
该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程ID。
一般来说,在f o r k之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
使用fork函数得到的子进程是父进程的处继承了整个进程的地址空间,包括:
进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
父、子进程之间的区别是:
fork的返回值;
进程I D、不同的父进程I D;
子进程的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;
父进程设置的锁,子进程不继承;
子进程的未决告警被清除;
子进程的未决信号集设置为空集。
Fork 例如:fork.c
cat fork.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t result;
result = fork();
if(result == -1)
{
perror("fork");
exit;
}
else if(result == 0)
{
printf("The return value is %d\n In child process!!\n My PID is %d\n",result,getpid());
}
else
{
printf("The return value is %d\n In father process!!\n My PID is %d\n",result,getpid());
}
}
1.7 vfork函数
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在f o r k之后经常跟随着exec。作为替代,使用了在写时复制( copy-on-Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的“页”,做一个拷贝。如:uclinux中的进程创建。
1.8 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 */);
int execv(const char * pathname, char *const a rgv [] );
int execle(const char * pathname, const char * a rg 0, .../* (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 */);
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
cat execlp.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0)
{
if(execlp("ps","ps","-ef",NULL)<0)
perror("execlp error!");
}
return 0;
}
cat execl.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(fork()==0)
{
if(execl("/bin/ps","ps","-ef",NULL)<0)
perror("execl error!");
}
return 0;
}
cat execle.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0)
{
if(execle("/bin/env","env",NULL,envp)<0)
perror("execle error!");
}
return 0;
}
vi execve.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *arg[]={"env",NULL};
char *envp[]={"PATH=/tmp","USER=sunq",NULL};
if(fork()==0){
if(execve("/bin/env",arg,envp)<0)
perror("execve error!");
}
return 0;
}
1.9 exit和_exit
exit和_exit用于中止进程;
_exit的作用:直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的数据结构;
exit与_exit函数不同,exit函数在调用exit系统之前要检查文件打开情况把文件缓冲区的内容写回文件中去。如调用printf()函数。
1.10 wait和waitpid函数
当一个进程正常或异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件(这可以在父进程运行的任何时候发生),所以这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生时即被调用执行的函数(信号处理程序)。对于这种信号的系统默认动作是忽略它。wait函数用于使父进程阻塞,直到一个子进程结束或者该进程接收到一个指定信号为止。
调用wait或waitpid的进程可能会:
阻塞(如果其所有子进程都还在运行)。
带子进程的终止状态立即返回(如果一个子进程已终止,正等待父进程存取其终止状态)。
出错立即返回(如果它没有任何子进程)
#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函数的区别:
在一个子进程终止前, wait 使其调用者阻塞,而waitpid 有一选择项,可使调用者不阻塞.
waitpid并不等待第一个终止的子进程—它有若干个选择项,可以控制它所等待的特定进程。
实际上wait函数是waitpid函数的一个特例.
对于waitpid的p i d 参数的解释与其值有关:
pid == -1 等待任一子进程. 于是在这一功能方面waitpid与wait等效.
pid > 0 等待其进程I D与p i d 相等的子进程.
pid == 0 等待其组I D等于调用进程的组I D的任一子进程.
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
2. 守护进程
2.1 概述
守护进程( daemon)是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止.因为它们没有控制终端,所以说它们是在后台运行的。linux系统有很多守护进程,它们执行日常事物活动.
2.2 守护进程特征
所有守护进程都以超级用户(用户I D为0)的优先权运行。
没有一个守护进程具有控制终端—终端名称设置为问号(?)、终端前台进程组I D设置为-1。缺少控制终端可能是精灵进程调用了s e t s i d的结果。
除u p d a t e以外的所有精灵进程都是进程组的首进程,对话期的首进程,而且是这些进程组和对话期中的唯一进程。u p d a t e是它所在进程组和对话期(中的唯一进程,但是该进程组的首进程(可能也是该对话期的首进程)已经终止。
所有这些守护进程的父进程都是i n i t进程。
2.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
3. 守护进程的出错处理
由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员。通常的办法是使用syslog服务,将出错信息输入到“/var/log/message”系统日志文件中去。
Syslog是linux中的系统日志管理服务通过守护进程syslog来维护。
3.1 syslog函数说明
Openlog函数用于打开系统日志服务的一个连接;
Syslog函数用于向日志文件中写入消息,在这里可以规定消息的优先级、消息的输出格式等;
Closelog函数用于关闭系统日志服务的连接。
3.1.1 syslog函数格式
(1)openlog函数
#include <syslog.h>
void openlog(char * ident, int option,int facility) ;
Ident :要向每个消息加入的字符串,通常为程序的名称;
option 参数:
LOG_CONS :若日志消息不能通过发送至syslogd ,则将该消息写至控制台;
LOG_NDELAY:立即打开UNIX域数据报套接口至syslsgd守护进程。通常,在记录第一条消息之前,该套接口不打开。
LOG_PERROR:除将日志消息发送给syslog外,还将它写至标准出错(stderr)。
LOG_PID:每条消息都包含进程ID,此选择项可供对每个请求都fork一个子进程的守护进程使用。
openlog的facility 参数
LOG_AUTH 授权程序: login.su,getty, ⋯
LOG_CRONcron 和 at
LOG_DAEMON 系统守护进程:ftpd,routed, ⋯
LOG_KERN 内核产生的消息
LOG_LOCAL0~7 保留由本地使用
LOG_LPR 行打系统:lpd, lpc, ⋯
LOG_MAIL 邮件系统
LOG_NEWSU senet网络新闻系统
LOG_SYSLOG syslogd守护进程本身
LOG_USER 来自其他用户进程的消息
LOG_UUCP UUCP系统
(2)syslog函数
#include <syslog.h>
void syslog(int priority, char *format, ...);
Priority选项(消息优先级)
LOG_EMERG 紧急(系统不可使用) (最高优先级)
LOG_ALERT 必须立即修复的条件
LOG_CRIT 临界条件(例如,硬设备出错)
LOG_ERR 出错条件
LOG_WARNING 警告条件
LOG_NOTICE 正常,但重要的条件
LOG_INFO 信息性消息
LOG_DEBUG 调试排错消息(最低优先级)
(3)closelog函数
#include <syslog.h>
void closelog(void);
守护进程日志系统见例:syslog_dema.c