进程控制详解

目录

1.进程的建立与运行

1.1进程的概念

1.2进程的建立

1.3进程的运行

 1.4数据和文件描述符的继承

2进程的控制操作

2.1进程的终止

2.2进程的同步

2.3进程终止的特殊情况

3进程的属性

3.1进程标识符

3.2进程的组标识符

3.3进程环境

3.4进程的当前目录

3.5进程的有效标识符

3.6进程资源 

3.7进程优先级

4.守护进程

4.1守护进程简介

4.2守护进程启动

4.3守护进程的错误输出

4.4守护进程的建立


1.进程的建立与运行

1.1进程的概念

在 UNIX 中,进程是正在执行的程序。它相当于 Windows 环境内的任务这一概念。每 个进程包括程序代码和数据。其中数据包含程序变量数据、外部数据和程序堆栈等。

一个进程对应于一个程序的执行,绝对不要把进程与程序这两个概念相混淆。 进程是动态的概念,而程序为静态的概念。多个进程可以并发执行同一个程序。

进程树的顶端是一个控制进程,它是一个名为 init 的 程序的执行,该进程是所有用户进程的祖先。

Linux 同样向程序员提供一些进程控制方面的系统调用,其中最重要的有以下几个:

  1. fork()。它通过复制调用进程来建立新的进程,它是最基本的进程建立操作。
  2. exec。它包括一系列的系统调用,其中每个系统调用都完成相同的功能,即通过用 一个新的程序覆盖原内存空间,来实现进程的转变。各种 exec 系统调用之间的区别仅在于 它们的参数构造不同。
  3. wait()。它提供了初级的进程同步措施,它能使一个进程等待,直到另一个进程结 束为止。
  4. exit()。这个系统调用常用来终止一个进程的运行。

1.2进程的建立

系统调用 fork()是建立进程的最基本操作,它是把 Linux 变换为多任务系统的基础。fork() 在 Linux 系统库 unistd.h 中的函数声明如下:

pid_t fork(void);

如果 fork()调用成功,就会使内核建立一个新的进程,所建的新进程是调用 fork()的进程的副本。

新建立的进程被成为子进程(child process),那个调用 fork()建立此新进程的进程被称为父进程(parent process)。以后,父进程与子进程就并发执行,它们都从 fork()调用后的 那句语句开始执行。

 系统调用 fork()没有参数,它返回一个 pid_t 类型的值 pid。pid 被用来区分父进程和子进程。在父进程中,pid 被置为一个非 0 的正整数;在子进程中,pid 被置为 0。

在父进程中,pid 中返回的数是子进程的进程标识符。所有的进程都是通过 fork()调用形成的,所以 每个 UNIX 进程都有自己的进程标识符,而且它是唯一的。

#include<iostream>
#include<unistd.h>
 using namespace std;

 int main(){
    pid_t pid;
    pid=fork();
    if(pid==0){
        cout<<"运行到这的是子程序"<<endl;
    }else if(pid>0){
        cout<<"运行到这的父程序"<<endl;
    }else{
        cout<<"进程创建错误"<<endl;
    }
 }

fork 调用后面的条件语句有三个分支:第一个分支对应于 pid 的值为零,它给出了子进程的工作;第二个分支对应于 pid 之值为正数,它给出了父进程的工作。第三个分支对 应于 pid 之值为负数(实际为-1),它给出了 fork 建立子进程失败时所作的工作。

1.3进程的运行

1.系统调用exec系列

exec 系列中的系统调用都完成相同的功能,它们把一个新程序装入 调用进程的内存空间,来改变调用进程的执行代码,从而形成新进程。如果 exec 调用成功, 调用进程将被覆盖,然后从新程序的入口开始执行。这就是说,exec没有建立一个与调用进程并发的新进程, 而是用新进程取代了原来的进程。下面给出了 exec 系列调用在 Linux 系统库中 unistd.h 中的函数声明:

int execl( const char *path, const char *arg, ...);

int execlp( const char *file, const char *arg, ...);

int execle( const char *path, const char *arg , ..., char* const envp[]);

int execv( const char *path, char *const argv[]);

int execvp( const char *file, char *const argv[])

后面只讨论exexl()函数。第一个参数 path 给出了被执行的程序所在的文件名,它必须 是一个有效的路径名,文件本身也必须含有一个真正的可执行程序。但是不能用 exec()l 来 运行一个 shell 命令组成的文件。第二个以及用省略号表示 的其它参数一起组成了该程序执行时的参数表。

#include<iostream>
#include<unistd.h>
 using namespace std;

 int main(){
   execl("/bin/ls","ls","-l",NULL);
   /*如果exec返回,则说明调用失败*/
   perror("execl failed to run ls");
   exit(1);
 }

exec 系列的其它系统调用给程序员提供使用 exec 功能的灵活性,它们能适用于多种形式的参数表。execv()只有两个参数:第一个参数指向被执行的程序文件的路径名,第二个 参数 argv 是一个字符型指针的数组,如下所示:

char *argv[]

这个数组中的第一个元素指向被执行程序的文件名(不含路径),剩下的元素指向程序所用的参数。因为该参数表的长度是不确定的,所以要用 null 指针作结尾。

#include<iostream>
#include<unistd.h>
 using namespace std;

 int main(){
   char *av[]={"ls","-l",NULL};
   execv("/bin/ls",av);
   perror("execv failed");
   exit(1);
 }

 系统调用 execlp()和 execvp()分别类似于系统调用 execl()和 execv(),它们的主要区别是:execlp()和 execvp()的第一个参数指向的是一个简单的文件名,而不是一个路径名。它 们通过检索 shell 环境变量 PATH指出的目录,来得到该文件名的路径前缀部分。另外,execlp 和 execvp 还可以用于运行 shell 程序,而不只是普通的程序。

2.对exec传送变量的访问

任何被 exec 调用所执行的程序,都可以访问 exec 调用中的参数。这些参数是调用 exec 的程序传送给它的。我们可以通过定义程序 main()函数的参数来使用这些参数,方法如下:

main( int argc, char* argv[] );

以上说明的 main()函数中,argc 是参数计数器,argv 指向参数数组本身。所以,用 execvp() 执行一个程序,如下所示:

chat* argin[]={“command”, “with”, “argument”, NULL};

 当 prog 程序启动后,它取得的 argc 和 argv 之值如下:

argc=3;

argv[0]=”command”;

argv[1]=”with”;

argv[2]=”argument”

argv[3]=NULL;

3.exec 和 fork()的联用

我们可以先用 fork()建 立子进程,然后在子进程中使用 exec,这样就实现了父进程运行一个与其不同的子进程,并 且父进程不会被覆盖。

下面我们给出一个 exec 和 fork()联用的例子:

#include<iostream>
#include<unistd.h>
#include<wait.h>
#include<sys/types.h>
 using namespace std;

 int main(){
   int pid;
   /*fork子进程*/
    pid=fork();
    switch(pid){
        case -1:
            perror("fork failed");
            exit(1);
        case 0:
            execl("/bin/ls","ls","-l",nullptr);
            exit(1);
        default:
            wait(nullptr);
            cout<<"is completes\n";
            exit(0);

    }
 }

在程序中,在调用 fork()建立一个子进程之后,马上调用了 wait(),使父进程在子进程结束之前,一直处于睡眠状态。

 1.4数据和文件描述符的继承

1.fork()、文件和数据

用系统 fork()建立的子进程几乎与其父进程完全一样。子进程中的所有变量均保持它们在父进程中之值(fork()的返回值除外)。因为子进程可用的数据是父进程可用数据的拷 贝,并且其占用不同的内存地址空间,所以必须要确保以后一个进程中变量数据的变化, 不能影响到其它进程中的变量。

在父进程中已打开的文件,在子进程中也已被打开,子进程支持这些文件的文件描述符。但是,通过 fork()调用后,被打开的文件与父进程和子进程存在着密切的联系, 这是以为子进程与父进程公用这些文件的文件指针。这就有可能发生下列情况:由于文件指针由系统保存,所以程序中没有保存它的值,从而当子进程移动文件指针时,也等于移动了父进程的文件指针。文件指针为两进程共用。

2.exec和打开文件

当一个程序调用 exec 执行新程序时,在程序中已被打开的文件,其在新程序中仍保持打开。这就是说,已打开文件描述符能通过 exec 被传送给新程序,并且这些文件的指针也 不会被 exec 调用改变。

这儿,我们要介绍一个与文件有关的执行关闭位(close-on-exec),该位被设置的话, 则调用 exec 时会关闭相应的文件。该位的默认值为非设置。例行程序 fcntl 能用于对这一 标志位的操作,下面的程序段给出了设置“执行关闭”位的方法。

int fd;

fd=open(“file”,O_RDONLY);

fcntl(fd,F_SETFD,1);

如果已经设置了执行关闭位,我们可以用下面的语句来撤销“执行关闭“位的设置, 并取得它的返回值:

res=fcntl(fd,F_SETFD,0);

如果文件描述符所对应的文件的“执行关闭位”已经被设置,则 res 为 1,否则 res 之值 为 0。

2进程的控制操作

2.1进程的终止

系统调用 exit()实现进程的终止。exit()在 Linux 系统函数库 stdlib.h 中的函数声明如下:

void exit(int status);

exit()只有一个参数 status,称作进程的退出状态,父进程可以使用它的低 8 位。exit() 的返回值通常用于指出进程所完成任务的成败。如果成功,则返回 0;如果出错,则返回非 0 值

exit()除了停止进程的运行外,它还有一些其它作用,其中最重要的是,它将关闭所有已打开的文件。如果父进程因执行了wait()调用而处于睡眠状态,那么子进程执行 exit()会 重新启动父进程运行。另外,exit()还将完成一些系统内部的清除工作,例如缓冲区的清除工作等。

除了使用 exit()来终止进程外,当进程运行完其程序到达 main()函数时,进程会自动 终止。当进程在 main()函数内执行一个 return 语句时,它也会终止。

在 Linux 中还有一个用于终止进程的系统调用_exit()。它在 Linux 系统函数库 unistd.h 中被声明:

void _exit(int status)

其使用方法与exit()完全相同,但是它执行终止进程的动作而没有系统内部的清除工作

2.2进程的同步

系统调用 wait()是实现进程同步的简单手段,它在 Linux 系统函数库 sys/wait.h 中的函数声明如下:

pid_t wait(int *status)

我们在前面已经看到了,当子进程执行时,wait()可以暂停父进程的执行,使起等待。 一旦子进程执行完,等待的父进程就会重新执行。如果有多个子进程在执行,那么父进程中的 wait()在第一个子进程结束时返回,恢复父进程执行。

wait()的返回值通常是结束的那个子进程的进程标识符。如果 wait()返回-1,表示没有子进 程结束,这时 errno 中含有出错代码 ECHILD

wait()有一个参数,它可以是一个指向整型数的指针,也可以是一个 null 指针,如果参数用了 null 指针,wait 就忽略它。如果参数是一个有效的指针,那么 wait 返回时,该指针就指向子进程退出时的状态信息。通常,该信息就是子进程通过 exit 传送出来的出口信息。下面的程序 status 就给出了这种情况下,wait 的使用方法。

#include<iostream>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
 using namespace std;

 int main(){
   int pid,status,exit_status;

   if(pid=fork()<0){
    perror("fork failed");
    exit(1);
   }
   if(pid==0){
    /*子进程*/
    sleep(4);
    exit(5);/* 使用非零值退出,以便主进程观察 */
   }
   /*父进程*/
   if(wait(&status)<0){
    perror("wait failed");
    exit(1);
   }
   /* 将 status 与 0xFF(255)与来测试低 8 位 */
   if(status&0xff){
    cout<<"Somne low-roderbits not zero\n";
   }else{
    exit_status=status >> 8;
    exit_status &=0xFF;
    cout<<"Exit status from"<<pid<<"was"<<exit_status<<endl;
   }
   exit(0);
 }

通过 exit 返回给父进程之值 存放在 exit_status 的低位中,为了使其有意义,exit_status 的高 8 位必须为 0,因此从 wait()返回后,就可以用按位与操作进行测试, 如果它们不为 0,表示该子进程是被另一个进程用一种称为信号的通信机构停止的,而不 是通过 exit()结束的。

2.3进程终止的特殊情况

我们在前面讨论了用 wait()和 exit()联用来等待子进程终止的情况。但是,还有两种进程终止情况值得讨论。这两种情况为:

  1. 子进程终止时,父进程并不正在执行 wait()调用(子进程终止,父进程没有回收子进程)。(僵尸进程)
  2. 当子进程尚未终止时,父进程却终止了。(孤儿进程)

在第一种情况中,要终止的进程就处于一种过渡状态(称为 zombie),处于这种状态的进程不使用任何内核资源,但是要占用内核中的进程处理表那的一项。当其父进程执行 wait()等待子进程时,它会进入睡眠状态,然后把这种处于过渡状态的进程从系统内删除, 父进程仍将能得到该子进程的结束状态。

在第二种情况中,一般允许父进程结束,并把它的子进程(包括处于过渡状态的进程) 交归系统的初始化进程所属。

3进程的属性

每个 Linux 进程都具有一些属性,这些属性可以帮助系统控制和调度进程的运行,以及维持文件系统的安全等。我们已经接触过一个进程属性,它就是进程标识符,用于在系统内标识一个进程。另外还有一些来自环境的属性,它们确定了进程的文件系统特权。

3.1进程标识符

系统给每个进程定义了一个标识该进程的非负正数,称作进程标识符。当某一进程终止后,其标识符可以重新用作另一进程的标识符。在任何时刻,一个标识符所代表 的进程是唯一的。系统把标识符 0 和 1 保留给系统的两个重要进程。

  • 进程 0 是调度进程, 它按一定的原则把处理机分配给进程使用。
  • 进程 1 是初始化进程,它是程序/sbin/init 的执行。

进程 1 是 UNIX 系统那其它进程的祖先,并且是进程结构的最终控制者。

利用系统调用 getpid 可以得到程序本身的进程标识符,其用法如下:

pid=getpid();

利用系统调用 getppid 可以得到调用进程的父进程的标识符,其用法如下:

ppid=getppid();

3.2进程的组标识符

Linux 把进程分属一些组,用进程的组标识符来知识进程所属组。进程最初是通过 fork() 和 exec 调用来继承其进程组标识符。但是,进程可以使用系统调用 setpgrp(),自己形成一 个新的组。setpgrp()在 Linux 系统函数库 unistd.h 中的函数声明如下:

int setpgrp(void);

setpgrp()的返回值newpg是新的进程组标识符,它就是调用进程的进程标识符。这时,调用进程就成为这个新组的进程组首,它所建立的所有进程,将继承 newpg 中的进程组标识符。

一个进程可以用系统调用 getpgrp()来获得其当前的进程组标识符,getpgrp()在 Linux 系统函数库 unistd.h 中的函数声明如下:

int setpgrp(void);

函数的返回值就是进程组的标识符。

当某个用户退出系统时,则相应的 shell 进程所启动的全部进程都要被强行终止。系统是根据进程的组标识符来选定应该终止的进程的。 如果一个进程具有跟其祖先 shell 进程相同的组标识符,那末它的生命期将可超出用户的注册期。这对于需要长时间运行的后台任务是十分有用的。

下面给出一个改变进程的组标识符的例子,它的效果相当于使用“不中止”程序 nohup 的效果。

int newpgid;

/*改变进程组*/

newpig=setpgrp();

3.3进程环境

进程的环境是一个以 NULL 字符结尾的字符串之集合。在程序中可以用一个以 NULL 结尾的字符型指针数组来表示它。系统规定,环境中每个字符串形式如下:

name=something

 Linux 系统提供了 environ 指针,通过它我们可以在程序中访问其环境内容。 在使用 environ 指针前,应该首先声明它:

extern char **environ;

下面的这段代码(showenv.c)演示了如何通过 environ 指针访问环境变量:

#include<iostream>
#include<unistd.h>
 using namespace std;
extern char **environ;
 int main(){
   char** env=environ;

   while(*env){
    cout<<*env++;
   }
   return 0;
 }

一个进程的初始环境与用 fork()或 exec 建立它的父进程环境相同。由于环境可以通过 fork()或者 exec 被传送,所以其信息被半永久性的保存。对于 新建立的进程来说,可以重新指定新的环境。

如果要为进程指定新的环境,则需要使用 exec 系列中的两种系统调用:execle()和 execve()。它们在 Linux 系统函数库 unistd.h中的函数声明如下:

int execle( const char *path, const char *arg , ..., char * const envp[]);

int execve (const char *filename, char *const argv [], char* const envp[]);

它们的调用方法分别类似于 execl()和 execv(),所不同的是它们增加了一个参数 envp, 这是一个以 NULL指针结束的字符数组,它指出了新进程的环境。下面的程序演示了execve() 的用法,它用 execve()把新的环境传送给上面的程序程序 showenv:

#include<iostream>
#include<unistd.h>
using namespace std;

 int main(){
    char* argv[]={"showenv",nullptr},
            *envp[]={"foo=bar","bar=foo",nullptr};
    execve("./showenv",argv,envp);
    perror("exeve failed.");
    return 0;
 }

在 Linux 的系统函数库 stdlib.h 中提供了一个系统调用 getenv()。根据参数给出的字符串 name,扫描环境内容,找出“name=string”这种形式 的字符串。如果成功,findenv()就返回一个指向这个字符串中”string”部分的指针。如果不成功,就返回一个 NULL 指针。另外还有一个与 getenv()相配对的系统调用 putenv(),它用于改变和扩充环境, 其使用方法为:

putenv(“newvariable=value”);

如果调用成功,其就返回零。需要注意的是,它只能改变调用进程的环境,而父进程 的环境并不随之改变。

3.4进程的当前目录

每个进程都有一个当前目录。一个进程的当前目录最初为其父进程的当前目录,可见 当前目录的初始值是通过 fork()和 exec 传送下去的。如果子进程通过 chdir()改变了它的当前目录,那么其父进程的当前目录并没有因此而改变。鉴于此原因,系统的 cd 命令(改变当前目录命令)实际上是一个 shell 自身 的内部命令,其代码在 shell 内部,而没有单独的程序文件。只有这样,才能改变相应 shell 进程的当前目录。否则的话,只能改变 cd 程序所运行进程自己的当前目录。

类似的,每个进程还有一个根目录,它与绝对路径名的检索起点有关。与当前目录一 样,进程的根目录的初始值为其父进程的根目录。可以用系统调用 chroot()来改变进程的根 目录,但是这不会改变其父进程的根目录。

3.5进程的有效标识符

每个进程都有一个实际用户标识符和一个实际组标识符,它们永远是启动该进程之用 户的用户标识符和组标识符。

进程的有效用户标识符和有效组标识符也许更重要些,它们被用来确定一个用户能否 访问某个确定的文件。在通常情况下,它们与实际用户标识符和实际组标识符是一致的。 但是,一个进程或其祖先进程可以设置程序文件的置用户标识符权限或置组标识符权限。 这样,当通过 exec 调用执行该程序时,其进程的有效用户标识符就取自该文件的 文件主的 有效用户标识符,而不是启动该进程的用户的有效用户标识符。

有几个系统调用可以用来得到进程的用户标识符和组标识符,详见下列程序:

uid_t uid,euid;

gid_t gid,egid;

. … .

/* 取进程的实际用户标识符 */

uid=getuid();

/* 取进程的有效用户标识符 */

euid=geteuid();

/* 取进程的实际组标识符 */

gid=getgid();

/* 取进程的有效组标识符 */

egid=getegid();

另外,还有两个系统调用可以用来设置进程的有效用户标识符和有效组标识符,它们 的使用格式如下:

uid newuid;

pid newgid;

int status;

/* 设定进程的有效用户标识符 */

status=setuid(newuid);

/* 设定进程的有效组标识符 */

status=getgid(newgid);

3.6进程资源 

Linux 提供了几个系统调用来限制一个进程对资源的使用。它们是 getrlimit(),setrlimit() 和 getrusage()。它们的的函数声明如下:

nt getrlimit (int resource, struct rlimit *rlim);

int getrusage (int who, struct rusage *usage);

int setrlimit (int resource, const struct rlimit *rlim);

其中,getrlimit 和 setrlimit 分别被用来取得和设定进程对资源的的限制。它们的参数是 相同的,第一个参数 resource 指定了调用操作的资源类型,

 第二个参数 rlim 用于取得/设定具体的限制。struct rlimit 的定义如下:

struct rlimit

{

        int rlim_cur;//是目前所使用的资源数

        int rlim_max;//是限制数(如果想取消某个资源的限制, 可以把 RLIM_INFINITY赋给 rlim 参数)

};

 如果调用成功,函数返回 0,否则返回-1。

只有超级用户可以取消或者放大对资源的限制。普通用户只能缩小对资源的限制。

系统调用 getrusage()返回当前的资源使用情况。其有两个参数:

第一个参数 who 指定了查看的对象,可以是:

  • RUSAGE_SELF 查看进程自身的资源使用状况。
  • RUSAGE_CHILDREN 查看进程的子进程的资源使用状况。

第二个参数 usage 用于接收资源的使用状况,rusage 结构的定义如下:

struct rusage

{

        struct timeval ru_utime; /* 使用的用户时间 */

        struct timeval ru_stime; /* 使用的系统时间 */

        long ru_maxrss; /* 最大的保留集合尺寸 */

        long ru_ixrss; /* 内部共享内存尺寸*/

        long ru_idrss; /* 内部非共享数据尺寸 */

        long ru_isrss; /* 内部非共享栈尺寸 */

        long ru_minflt; /* 重复声明页 */

        long ru_majflt; /* 错误调用页数 */

        long ru_nswap; /* 交换区 */

        long ru_inblock; /* 阻塞的输入操作数 */

        long ru_oublock; /* 阻塞的输出操作数 */

        long ru_msgsnd; /* 发送的消息 */

        long ru_msgrcv; /* 接受的消息 */

        long ru_nsignals; /* 接受的信号 */

        long ru_nvcsw; /* 志愿上下文开关 */

        long ru_nivcsw; /* 非志愿上下文开关 */

};

函数调用成功,返回 0,否则返回-1。

3.7进程优先级

系统以整型变量 nice 为基础,来决定一个特定进程可得到的 CPU 时间的比例。nice 之 值从 0 至其最大值。我们把 nice 值称为进程的优先数。进程的优先数越大,其优先权就越低。普通进程可以使用系统调用 nice()来降低它的优先权,以把更多的资源分给其它进程。 具体的做法是给系统调用 nice 的参数定一个正数,nice()调用将其加到当前的 nice 值上。 例如:

nice(5);

这就使当前的优先数增加了 5,显然,其对应进程的优先权降低了。
超级用户可以用系统调用 nice()增加优先权,这时只需给 nice()一个负值的参数,如:

nice(-1);

4.守护进程

4.1守护进程简介

守护进程是一种后台运行并且独立于所有终端控制之外的进程。UNIX/Linux 系统通常 有许多的守护进程,它们执行着各种系统服务和管理的任务。

4.2守护进程启动

要启动一个守护进程,可以采取以下的几种方式:

  1. 在系统期间通过系统的初始化脚本启动守护进程。这些脚本通常在目录 etc/rc.d 下, 通过它们所启动的守护进程具有超级用户的权限。系统的一些基本服务程序通常都是通过这种方式启动的。
  2. 很多网络服务程序是由 inetd 守护程序启动的。它监听各种网络请求,如 telnet、ftp 等,在请求到达时启动相应的服务器程序(telnet server、 ftp server 等)。
  3. 由 cron 定时启动的处理程序。这些程序在运行时实际上也是一个守护进程。
  4. 由 at 启动的处理程序。
  5. 守护程序也可以从终端启动,通常这种方式只用于守护进程的测试,或者是重起因 某种原因而停止的进程。
  6. 在终端上用 nohup 启动的进程。用这种方法可以把所有的程序都变为守护进程,但 在本节中我们不予讨论。

4.3守护进程的错误输出

守护进程不属于任何的终端,所以当需要输出某些信息时,它无法像通常程序那样将 信息直接输出到标准输出和标准错误输出中。这就需要某些特殊的机制来处理它的输出。 为了解决这个问题,Linux 系统提供了 syslog()系统调用。通过它,守护进程可以向系统的 log 文件写入信息。它在 Linux 系统函数库 syslog.h 中的定义如下:

void syslog( int priority, char *format, ...);

该调用有两个参数: priority 参数指明了进程要写入信息的等级和用途,可以的取值如表 3-2 所示:

 如果等级没有被指定,就自动取缺省值 LOG_NOTICE。

 如果没有指定用途,缺省的 LOG_USER 就自动被指定。

syslog()调用后面的参数用法和 printf()类似,message 是一个格式串,指定了记录输出 的格式。需要注意的是在这个串的最后需要指定一个%m,其对应着 errno 错误码。
下面是一个例子:

syslog(LOG_INFO|LOG_LOCAL2,”rename(%s,%s): %m”,file1,file2);

在一个进程使用 syslog()的时候,应该先使用 openlog()打开系统记录:

void openlog(const char *ident, int options, int facility);

参数 ident 是一个字符串,它将被加在所有用 syslog()写入的信息前。通常这个参数是 程序的名字。 参数 options 可以是表 3-4 这些参数或是它们的或(|)的结果:

 参数 facility 指定了 syslog()调用的缺省用途值。
在使用完 log 之后,可以使用系统调用 closelog()来关闭它:

void closelog(void);

4.4守护进程的建立

  1. fork
    首先需要 fork 一个子进程并将父进程关闭。如果进程是作为一个 shell 命令在命令行上 前台启动的,当父进程终止时,shell 就认为该命令已经结束。这样子进程就自动称为了后台进程。而且,子进程从父进程那里继承了组标识符同时又拥有了自己的进程标识符,这 样保证了子进程不会是一个进程组的首进程。这一点是下一步 setsid 所必须的。
  2. setsid
    setsid()调用创建了一个新的进程组,调用进程成为了该进程组的首进程。这样,就使该进程脱离了原来的终端,成为了独立于终端外的进程。
  3. 忽略 SIGHUP 信号,重新 fork
    这样使进程不在是进程组的首进程,可以防止在某些情况下进程意外的打开终端而重 新与终端发生联系。
  4. 改变工作目录,清除文件掩码
    改变工作目录主要是为了切断进程与原有文件系统的联系。并且保证无论从什么地方 启动进程都能正常的工作。清除文件掩码是为了消除进程自身掩码对其创建文件的影响。
  5. 关闭全部已打开的文件句柄
    这是为了防止子进程继承了在父进程中打开的文件而使这些文件始终保持打开从而产生某些冲突。
  6. 打开 log 系统

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值