linux 进程学习整理,与备后记.
1、程序和进程
1.1 程序
程序(program)是存放在磁盘文件中的可执行文件。
1.2 进程和进程ID
程序的执行实例被称为进程(process)。某些操作系统用任务表示正被执行的程序。
每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一非负整数。
1.3 linux下的进程结构
Linux系统是一个多进程的系统,进程之间具有并行性、互不干扰的特点。
linux中进程包含3个段,分别为“代码段”、“数据段”和“堆栈段”。
“数据段”存放全局变量、常数以及动态数据分配的空间(malloc函数取得的空间);
“代码段”存放程序代码;
“堆栈段”存放子程序的返回地址、子程序的参数以及程序的局部变量。
[init进程]
1.5 fork函数
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程I D,出错为-1
1.6 进程创建
由fork创建的新进程被称为子进程( child process)。
该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程ID。
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
使用fork函数得到的子进程是父进程的处继承了整个进程的地址空间,包括:
进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。
子进程的未决信号集设置为空集。
1.7 vfork函数
vfork函数的调用序列和返回值与fork相同,但两者的语义不同。
现在很多的实现并不做一个父进程数据段和堆的完全拷贝,因为在f o r k之后经常跟随着exec。作为替代,使用了在写时复制( copy-on-Write, COW)的技术。这些区域由父、子进程共享,而且内核将它们的存取许可权改变为只读的。如果有进程试图修改这些区域,则内核为有关部分,典型的是虚存系统中的“页”,做一个拷贝。如:uclinux中的进程创建。
1.8 exec函数
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;
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函数的区别]:
waitpid函数提供了wait函数没有提供的三个功能:
(1) waitpid等待一个特定的进程(而w a i t则返回任一终止子进程的状态)。
(2) waitpid提供了一个w a i t的非阻塞版本。有时希望取得一个子进程的状态,但不想阻塞。
(3) waitpid支持作业控制(以WUNTRACED选择项)。
2.守护进程
2.1 概述
守护进程( daemon)是生存期长的一种进程。它们常常在系统引导装入时起动,在系统关闭时终止。因为它们没有控制终端,所以说它们是在后台运行的。linux系统有很多守护进程,它们执行日常事物活动。
2.2 守护进程特征
首先做的是调用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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#define MAXFILE 65535
int main()
{
pid_t pc;
int i,fd,len;
char *buf="This is a Dameon\n";
len =strlen(buf);
pc=fork();
if(pc<0)
{
printf("error fork\n");
exit(1);
}
else if(pc>0)
exit(0);
setsid();
chdir("/");
umask(0);
for(i=0;i<MAXFILE;i++)
close(i);
while(1)
{
/*open() 基于非缓存模式, fopen() 基于缓存模式*/
if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
{
perror("open");
exit(1);
}
write(fd, buf, len+1);
close(fd);
sleep(10);
}
}
3 守护进程的出错处理
由于守护进程完全脱离了控制终端,因此,不能像其他程序一样通过输出错误信息到控制台的方式来通知程序员。
通常的办法是使用syslog服务,将出错信息输入到“/var/log/message”系统日志文件中去。
Syslog是linux中的系统日志管理服务通过守护进程syslog来维护。
3.1.1 syslog函数格式
(1)openlog函数
openlog的第一个参数ident将是一个标记,ident所表示的字符串将固定地加在每行日志的前面以标识这个日志,通常就写成当前程序的名称以作标记。
第二个参数option是下列值取与运算的结果:LOG_CONS, LOG_NDELAY, LOG_NOWAIT, LOG_ODELAY, LOG_PERROR, LOG_PID
第三个参数指明记录日志的程序的类型。
#include <syslog.h>
void openlog(char * ident, int option, int facility) ;
Ident:要向每个消息加入的字符串,通常为程序的名称;
option参数:
#include <syslog.h>
void syslog(int priority, char *format, ...);
第一个参数是消息的紧急级别,第二个参数是消息的格式,之后是格式对应的参数。就是printf函数一样使用。
如果我们的程序要使用系统日志功能,只需要在程序启动时使用openlog函数来连接syslogd程序,后面随时用syslog函数写日志就行了
Priority选项(消息优先级)
#include <syslog.h>
void closelog(void);
例子: syslog_dameon.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<fcntl.h>
#include<sys/types.h>
#include<unistd.h>
#include<sys/wait.h>
#include<syslog.h>
#define MAXFILE 65535
int main()
{
pid_t pc,sid;
int i,fd,len;
char *buf="This is a Dameon\n";
len =strlen(buf);
pc=fork();
if(pc<0)
{
printf("error fork\n");
exit(1);
}
else if(pc>0)
exit(0);
openlog("demo_update",LOG_PID, LOG_DAEMON);
if((sid=setsid())<0)
{
syslog(LOG_ERR, "%s\n", "setsid");
exit(1);
}
if((sid=chdir("/"))<0)
{
syslog(LOG_ERR, "%s\n", "chdir");
exit(1);
}
umask(0);
for(i=0;i<MAXFILE;i++)
close(i);
while(1)
{
if((fd=open("/tmp/dameon.log",O_CREAT|O_WRONLY|O_APPEND, 0600))<0)
{
syslog(LOG_ERR, "open");
exit(1);
}
write(fd, buf, len+1);
close(fd);
sleep(10);
}
closelog();
exit(0);
}