1 linux进程概述

进程简单的说就是一个程序一次执行的过程,它是一个动态的概念。按照教科书上的定义,进程是程序执行的实例,是linux的基本调度单位。 
对于程序员来说,最重要的就是要区分进程和程序的区别,程序是指一段完成功能的代码,或者说是一个工具,它是一个静态的概念,而进程,它是动态的,比如,linux的vi编辑器,它就是一段在linux下用于文本编辑的工具,那么它是一个程序,而我们在linux终端中,可以分别开启两个vi编辑器的进程。一旦提到进程,我们的脑子里就应该产生——程序从代码的第一句动态的执行到最后一句这样的一个思路。 
一个进程由如下元素组成: 
1.> 进程的当前上下文,即进程的当前执行状态; 
2.> 进程的当前执行目录 
3.> 进程访问的文件和目录 
4.> 进程的访问权限,比如它的文件模式和所有权 
5.> 内存和其他分配给进程的系统资源 
在linux系统中,内核使用进程来控制对CPU和其他系统资源的访问,并且使用进程来决定在CPU上运行哪个程序,运行多久以及采用什么特性运行它。内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。 
OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。进程除了自身的ID外,还有父进程ID(ppid),所有进程的祖先进程是同一个进程,它叫做init进程,ID为1,init进程是内核自检后的一个启动的进程。init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。 
简单来说,Linux的进程就相当于Windows系统中的任务,每个在linux下运行的程序都是一个进程,每个进程包含(属于自己唯一的)进程标识符及数据,这些数据包含进程变量,外部变量及进程堆栈等。 
Linux常见进程命令

ps

查看系统中的进程

top

动态的现实系统中的进程

nice

按用户的指定的优先级运行

renice

改变正在运行进程的优先级

crontab

用于安装、删除或者列出用于驱动cron后台进程

kill

终止进程

bg

将挂起的进程放到后台执行

2 进程控制

所谓控制,就是将进程的一个完整的生命周期(进程的诞生,繁衍与消亡)完全的控制在在程序员的手中。 
刚才提到,在Linux 环境下启动进程时,系统会自动分配一个唯一的数值给这个进程,这个数值就是这个进程的标识符。 
在Linux 中主要的进程标识符有进程号(PID),和它的父进程(PPID)(parents process ID)。PID和PPID都是非零的正整数,在linux中获得当前的进程PID和PPID的系统调用函数为getpid和getppid 。

getpid系统调用说明:

所需头文件

#include <unistd.h>

函数功能:

取得当前进程的进程号

函数原型:

int getpid();

函数传入值:

函数返回

成功返回当前进程的进程号,失败将错误包含在perror中

备注:

 

getppid系统调用说明:

所需头文件

#include <unistd.h>

函数功能:

取得当前进程的父进程号

 

函数原型:

int getppid();

函数传入值:

函数返回

成功返回当前进程的父进程号,失败将错误包含在perror中

备注:

 

设计一个程序,显示其进程的标识符PID以及其父进程的标识符ppid! 
文件源代码如下:

#include <stdio.h>
#include <unistd.h>
int main(){
               printf("系统分配的进程号是:%d\n",getpid());
               printf("系统分贝的父进程号是:%d\n",getppid());
               return 0;
}

 

3 进程的创建

fork() 
fork系统调用在linux中视最重要的系统调用之一,是我们实现进程控制的最常用的系统调用,并且,很多其他的系统调用也要使用到它! 
fork系统调用说明

所需文件头:

#include <unistd>
#incude <sys/types.h>

函数功能:

建立一个新进程

函数原型:

Pid_t fork(void);

函数传入之值

函数返回值

执行成功则建立一个新的进程,在子进程中则返回0,在父进程中回返回新建子进程的进程号(PID),失败则返回-1

备注:

新进程的信息是复制而来,而非指相同的内存空间,因子进程对这些变量的修改和父进程并不同步。

我们知道,进程必须有依托生存的环境,也就是其父进程,每一个进程必须在其父进程的照顾下才能生存,一旦失去其依托的进程,那么进程就失去了原有的功能,成为一个即浪费资源,占用空间而又没有任何贡献的行尸走肉——我们叫它——僵死进程。 
fork函数的作用是在其进程中创建一个新的进程,这个新的进程不会取代原来的进程,而是以当前进程的一个子进程而存在的。这个子进程会有一个新的进程标识符pid。并且这个子进程会继承父进程的一切! 
什么是叫继承父进程的一切呢?就是克隆父进程的所有,包括父进程的代码,父进程正在执行的状态,父进程的工作目录,父进程的所有资源等等,一切的一切,fork系统调用会全部的复制下来。 
我们在来看fork函数的返回值,它好像有两个返回值,其实是一个,在调用fork系统调用后,原先的进程空间从一个变成两个,而fork函数在不同的进程空间会返回不同的值,在子进程的进程空间中,fork返回 0 ,而在父进程的进程中则返回刚刚新建的子进程的进程标识符,也就是子进程的pid。 
我们看一个例子,这样能更好帮我们理解fork函数的作用! 
要求设计一个程序,要求在显示当前目录下的文件信息,然后测试到一个任意社区的网络连接状况。 

程序源代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
               pid_t result;
               result=fork();
               int newret;
               if(result ==-1){
               perror("创建子进程失败");
               exit;
}
               else if(result ==0) /*返回值为零,为子进程 */
{              
                    printf("返回值是:%d,这是子进程!\n此进程ID号(pid)是:%d\n此进程的父进程的ID(ppid)号是:%d\n",result,getpid(),getppid());
               newret = system("ls -l");
               }
               else{
               printf("返回值是:%d,这是父进程!\n此进程ID号(pid)是:%d\n此进程的父进程的ID(ppid)号是:%d\n",result,getpid(),getppid());
               newret = system ("ping www.163.com");
}
}

我们看到两个进程,父进程和子进程,父进程是4-3,而子进程是使用fork系统调用创建出来的子进程。 
在父进程中打印了父进程自己的ID号,并且打印了子进程的ID号,然后做了一个ping命令,用于查看到163是否连通的测试! 
在子进程中,子进程同时打印了自己的进程号和父进程的进程号,并做了一个ls当前目录的操作。

我们看到好像程序是在一个进程中完成的,其实,它是在两个进程中分别完成的。

下面,我们将看到一个有意思的东西,但是,这必须建立在我们理解了fork系统调用之后! 
我们知道fork会创建子进程,一个和父进程一样但是完全独立的子进程。那么我们下面的代码:

for( ; ; )fork():

这是一个无限循环创建子进程的语句,我们想象一下,一旦我们把这样的语句写在一个程序中并且执行程序,那么那个进程就会在无限循环中创建子进程。 
那么系统资源很快就会被不断创建的子进程占满,使系统无法继续正常工作,那么,以上就是一个简单的系统炸弹! 
很简单,一句代码,你就可以做步入了黑客的殿堂! 
当然,这里真正的黑客还差的很远,上面的程序破解起来也很简单,只要系统管理员对用户可以调用的进程数量进行限制就是破解! 
有兴趣的同学可以自己下去实验!这里就不多讨论了!

4 进程的终止

进程在执行结束时并不会自己结束,而需要使用进程终止的系统调用:“exit”与“_exit”.
我们在前面的程序中已经使用过“exit”系统调用,其功能就是退出进程,用法十分简单,但是我们还没见过“_exit”,在这里我们需要详细探讨一下它的使用以及这两个系统调用的区别。 
我们直接通过一段程序来看其使用效果: 

程序源代码如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){
      pid_t result;
      result = fork();
      if(result == -1){
      perror("创建子进程失败");
      exit(0);
}
      else if(result == 0)
{
      printf("测试中止进程函数_exit函数!\n");
      printf("这一句我们来看看缓存的结果!");
      _exit(0);
}
      else {
      sleep(10);
      printf("测试中止进程的函数exit!\n");
      printf("这一句我们用来监察最后使用不通函数而得到的不通结果!");
      exit(0);
}
}

我们使用父子进程来分别调用这两个终止进程的系统调用,在子进程中我们调用_exit来终止子进程,在父进程中,我们使用exit来终止父进程。

我们看到,使用_exit系统调用退出的进程少打了一句话:“这一句我们用来看看缓存的结果” 子进程在使用_exit之前没有执行printf函数吗?其实执行过了,printf函数是基于流操作的函数,也就是说他使用缓存,那么他的工作原理,就是先把要输出的语句全部放在缓存上,然后等待一个标志在把缓存上的数据一口气输出到屏幕上,在子进程中,第一个printf语句有换行标志,那么当printf看到换行时,会把放在缓存上的信息输出到屏幕,而第二句,printf会一直等待一个标志,不过很可以,在没有等待到这个标志的时候,进程就被强制使用_exit系统调用退出了!由此printf抱憾终身! 
_exit系统调用的作用是直接使进程终止,清除其使用的内存空间,并清除其在内核中的各种数据结构。 
而exit系统调用是一个安全的退出的函数。exit()函数则在退出前自动增加若干到工序,比如,系统在退出之前,它要查看查看文件的打开情况,并把缓冲区的内容写回文件,以便更好的保护数据的完整性。在确保所有文件安全后,exit才清除其在内核中的各种数据结构,并结束进程的运行。因此,在父进程中第二句printf虽然也没有标志告诉printf输出,但是exit在清空内存之前,做了一系列的安全检查,在确保进程的功能完全实现后,或者说文件安全后,在清空内存,终止进程。

5 特殊进程

 5.1僵尸进程

僵尸进程(zombie)是指已终止运行,但尚未被清除的进程。 
前面的学习中,我们已经了解了父进程和子进程的概念,并已经掌握了系统调用exit的用法,我们知道exit系统调用是安全终止进程的系统调用,它在真正终止进程之前要进行一些列的检查,其实,在一个进程调用了exit之后,该进程并完全消失掉(终止),而是留下一个称为僵尸进程(Zombie)的数据结构。僵尸进程是非常特殊的一种进程,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。从这点来看,僵尸进程虽然有一个很酷的名字,但它的影响力远远抵不上那些真正可怕的僵尸兄弟,它除了在进程列表表中留下一个供人凭吊的信息,其它对系统毫无作用。 
让我们来看一眼Linux里的僵尸进程究竟长什么样子。 
当一个进程已终止,但其父进程还没有调用系统调用wait(稍后介绍)对其进行收集之前的这段时间里,它会一直保持僵尸状态,利用这个特点,我们来写一个简单的小程序: 
设计一个程序,要求使父进程休眠并没有回收子进程之前,子进程调用exit系统调用终止进程。 

程序源代码如下:

#include <sys/types.h>
#include <sys/wait.h> 
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
pid_t pid;

pid=fork();

if(pid<0)
printf("error occurred!n");
else if(pid==0) /* 如果是子进程 */
exit(0);
else /* 如果是父进程 */
sleep(60); /* 休眠60秒,这段时间里,父进程暂停 */
wait(NULL); /* 收集僵尸进程 */
}


我们看到,这是,父进程在休眠状态,还没有退出,但是子进程已经使用系统调用exit终止进程了 
我使用使用查看进程的命令查看当前系统的进程列表:
在另一个终端中,输入:

ps -aux

用黑线画出来的进程就是4-5子进程终止后留下的僵尸进程了!我们可以看到他的进程状态使用Z+来标记,说明他是一个僵尸进程。 
注:linux上进程的5种状态: 
1. 运行(正在运行或在运行队列中等待) 
2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号) 
3. 不可中断(收到信号不唤醒和不可运行, 进程必须等待直到有中断发生) 
4. 僵死(进程已终止, 但进程描述符存在, 直到父进程调用wait()系统调用后释放)
5. 停止(进程收到SIGSTOP, SIGSTP, SIGTIN, SIGTOU信号后停止运行运行) 
ps工具标识进程的5种状态码: 
D 不可中断 uninterruptible sleep (usually IO) 
R 运行 runnable (on run queue) 
S 中断 sleeping 
T 停止 traced or stopped 
Z 僵死 a defunct ("zombie") process 
其它状态还包括W(无驻留页), <(高优先级进程), N(低优先级进程), L(内存锁页)等.  
现在,我们对exit系统调用有了新的认识:exit系统调用,它的作用是使进程终止,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁。 
僵尸进程虽然对其他进程几乎没有什么影响,不占用CPU时间,消耗的内存也几乎可以忽略不计,但有它在那里呆着,还是让人觉得心里很不舒服。而且Linux系统中进程数目是有限制的,在一些特殊的情况下,如果存在太多的僵尸进程,也会影响到新进程的产生。 
但是,为什么会有僵尸进程出现呢?当初设计UNIX(linux的僵尸进程是从unix中够继承的来的)的工程师仅仅是闲来无聊想烦烦其他的程序员吗?其实不然,我们可以想象,如果一个进程终止后,而此时程序员或系统管理员需要用到一些进程的相关信息,比如这个进程是怎么死亡的?是正常退出呢,还是出现了错误,还是被其它进程强迫退出的?其次,这个进程占用的总系统CPU时间和总用户CPU时间分别是多少?发生页错误的数目和收到信号的数目。如果一个进程终止所有与之相关的信息都立刻归于无形,那要使用上面所说的信息,就只好干瞪眼了。然而这些信息都被设计存储在僵尸进程中,如果需要,只要进程相关的资源回收就可以了! 
但是,如果我们不需要回收这样的信息,并且系统中已经有了太多的僵尸,很多僵尸根本毫无用处,那么如何消灭这些僵尸进程呢? 
那么,总结起来,我们有两个任务:

  1. 对于必要的资源信息,我们需要先收集信息,再结束僵尸进程;
  2. 对于根本不用的僵尸进程,我们直接结束僵尸进程。

不管是上面的哪一种情况,都要靠我们下面要讲到的waitpid调用和wait调用。这两者的作用都可以收集僵尸进程留下的信息,同时使这个进程彻底消失。下面就对这两个调用分别作详细介绍。 
我们在父进程中可以调用wait或waitpid函数,这两个系统调用自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到     这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 
wait系统调用: 
wait会返回被收集的子进程的进程ID(如果执行成功),如果调用进程没有子进程,调用就会失败,此时wait返回-1。

wait系统调用说明:


所需文件头

#include<sys/types.h>
#include<sys/wait.h>

函数原型:

Pid_t wait(int *status)

函数传入值

这里的status是一个整型指针,用于保存该进程退出的状态

返回值:

成功:子进程的进程号
失败:-1

当我们不需要进程的相关信息的时候,我们可以使用NULL参数,也就是: 
pid = wait(NULL);
当我们需要收集相关资源信息的时候,就需要用到status参数。当参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中,这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,由于这些信息被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,于是,人们就设计了一套专门的宏(macro)来完成这项工作,我们学习其中最常用的两个: 
● WIFEXITED(status) 
这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值 
● WEXITSTATUS(status) 
当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status)就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。请注意,如果进程不是正常退出的,也就是说,WIFEXITED返回0,这个值就毫无意义。 
僵尸进程回收实例: 

程序源代码如下:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
       int status;
       pid_t pc,pr;
       pc =fork();
       if (pc<0){
       perror("进程创建失败");
       exit(0);
       }else{
              if(pc==0){
              printf("这是子进程%d\n",getpid());
              sleep(3);
              exit(0);
              }else if(pc>0){
                     pr=wait(&status);
                     if(WIFEXITED(status)){
                            printf("子进程%d正常终止,",pr);
                            printf("中止时的状态是:%d\n",WEXITSTATUS(status));
                     }else{
                            printf("子进程%d非正常中止\n",pr);
                     }
              }
       }
       return 0;                
       }  
}

我们看到,程序将子进程退出时的状态打印出来了! 
我们再来看看Waitpid系统调用 
Waitpid系统调用说明:

所需文件头

#include <sys/types.h>
#include<sys/wait.h>

函数原型

Pid_t waitpid(pid_t pid , int*status,int options)

函数传入值:

pid

Pid>0: 只等待进程ID等于子pid的子进程,不管已经有其他子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。

Pid=-1:等带任何一一个子进程结束。此时和wait作用一样。

Pid=0:等待其组ID等于调用进程的组ID的任一子进程

Pid < -1: 等待其组ID等于pid的绝对值得任一进程

stuts

等同于wait的status.

option

WNOHANG:若由pid指定的子进程没有终止,则立刻返回0,不予等待。

WUTRACED若实现某支持作业控制,则由pid指定的任何一子进程状态已暂停,且其状态自暂停以来还未报告过,则返回其状态。

0,同wait,阻塞父进程,等待子进程退出

函数返回值:

正常:子进程的进程号

使用选项WNOHANG且没有子进程退出:0

出错:-1

waitpid的返回值比wait稍微复杂一些,一共有3种情况: 
● 当正常返回的时候,waitpid返回收集到的子进程的进程ID; 
● 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0; 
● 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在; 
当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回 
我们仔细比较一下wait与waitpid,就会发现wait函数实际上waitpid函数的一个特例。wait实际上是在内部利通特殊参数调用waitpid系统调用。 
我们看一个例子: 

程序源代码如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(){
       pid_t pc, pr;
       pc = fork();
       if(pc<0)
         printf("创建子进程失败\n");
       else if (pc==0){
              sleep(10);
              exit(0);
}
       else {  /*使用一个循环来测试子进程是否退出*/
             do{
               pr = waitpid (pc,NULL,WNOHANG);
                 if(pr==0){
                     printf("依附于此进程的子进程还没有中止.\n");
                     sleep(1);
}
}while (pr==0);
       if(pr==pc){
              printf("反回了子进程的ID:%d,说明子进程已经中止.僵尸资源已经回收,现在可以安全的中止父进程了\n",pr);
              exit(0);}
       else
              printf("some error occured\n");
}
}

父进程经过10次失败的尝试之后,终于收集到了退出的子进程。 

5.2守护进程

守护进程,也就是通常所说的daemon 进程,是Linux 中的后台服务进程,它运行在后台,并且一直在运行的一种特殊的进程。它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。 
Linux 的大部分服务器就是用守护进程实现的,比如:Internet服务器,web服务器。 
在Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,那么这个终端就称为这个进程的控制终端。当控制终端被关闭时,相应的进程就会随之自动关闭。 
但是守护进程却能够突破这种界限,它从被执行开始运转,直到整个系统关闭时才会退出。因此:守护进程有如下特点: 
守护进程常常是在系统引导装入时启动,且在后台运行,在系统关闭时终止。 
那么,如果我们想让某个进程不因为用户或终端或其他的变化而受到影响,那么就需要把这个进程变成一个守护进程。 
后台运行运行时守护进程的首要特性,其次,守护进程必须与其运行前的环境隔离开来,这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程中继承下来的。最后,守护进程有属于自己的启动方式,它可以在linux系统启动时从启动脚本/etc/rc.d/rc.local中启动。 
在linux环境下,有哪些内核守护进程,通过 ps –aux命令就可以查看 
我们主要要认识一个守护进程 init
init系统守护进程,他是进程1 ,负责启动运行层次特定的系统服务,这些服务通常是在他们自己拥有的守护进程的帮助下实现的。(可以说,这个进程是所有其他进程的父进程)。 
(注意:大多数进程都是以超级用户(用户ID 号为0)特权运行,并且在后台运行其所对应的终端号显示为问号(?)。) 

用黑线标出的就是我们的init守护进程,永远要记住的是:它是我们linux系统的所有进程的父进程。 
在了解了守护进程的概念之后,我们的目的又是什么呢?我们要学习如何编写守护进程。 
在前面的学习中,我们学习了僵尸进程,那么僵尸进程除了保存了一些相关的进程终止信息外,还有什么用处呢?它最重要的用法,就是帮助我们编写守护进程。记住一句话:守护进程是由僵尸进程改造而来的! 
编写守护进程 
编写守护进程,相当于编写一个系统服务,编写守护进程的流程: 
1 > 首先,创建子进程,终止父进程。 
由于守护进程是脱离终端控制的,因此首先复制子进程,中止父进程,使得程序在shell里造成一个已经运行完毕的假象。之后所有的工作都在子进程中完成。这时候,程序进程是一个僵尸进程,在形式上做了与控制终端的脱离。 代码如下:

pid =fork();
      if (pid>0)
             exit(0);

2 > 在子进程中创建一个新的会话。 
在调用fork函数时,子进程会全盘拷贝父进程的会话期,进程组,控制终端等,虽然父进程退出了,但是原先的会话期,进程组,控制终端等并没有改变,因此,并不是真正意义的独立开来。 
进程组:是一个或多个进程的集合,进程组由进程组ID来唯一标识。除了进程号,进程组ID也是一个进程的必备属性。 
会话期:会话组是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行所有进程都属于这个会话期。 
使用setsid函数能够是进程完全独立开来,从而脱离所有其他进程的控制。setsid用于创建一个新的会话,并担任该会话组的组长。调用setsid有三个作用:
    1,让进程摆脱原会话控制; 
2,让进程摆脱进程组的控制; 
3,让进程摆脱原控制终端的控制。 
3 > 改变工作目录 
调用fork函数子进程也会拷贝父进程的工作目录,通常的做法是把“/”目录作为守护进程的当前工作目录。特许需要则,也可以使用其他的工作目录。 
改变工作目录的函数是:chdir.
4 > 重设文件创建掩码 
      fork函数会复制父进程的文件掩码,这就给该子进程使用文件带来了诸多麻烦,因此,把掩码设置为0,可以大大曾加该守护进程的灵活性。设置掩码的函数是umask。 
5> 关闭文件描述符 
   如果父进程已经打开一些文件,那么子进程会复制这些已经打开的文件,但是,这些被打开的文件可能永远不会被守护进程读或写,但是打开一样消耗系统资源,可能导致所在文件系统无法卸载。用如下方法关闭文件描述符。

for(i=0;i<NOFILE;i++)
close(i);
或者 
for(i=0;i<MAXFILE;i++)
close(i);

       
我们来看一个实例: 
       例 6-1 创建一个守护进程,此进程在像/tmp下属写日志文件。 
步骤1: 编写守护进程创建函数:

[root@localhost char2]vim init.c

程序源代码如下:init.c守护进程创建函数

#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/stat.h>
void init_daemon(void){
               pid_t child1,child2;
               int i ;
               child1=fork();
               if(child1>0)
                               exit(0);
                 else if(child1<0)
                 {
                    perror("创建子进程失败");
                    exit(1);
                               }
               setsid();
               chdir("/tmp");
               umask(0);
               for(i=0;i<NOFILE;++i)
               close(i);
               return;
}

#include <stdio.h>
#include <time.h>

void init_daemon(void);
int main(){
               FILE *fp;
               time_t t;
               init_daemon();
               while(1){
                 sleep(10);
                 fp=fopen("6-1.log","a+");
                 if(fp>=0){
                   t=time(0);
                   fprintf(fp,"守护进程仍在运行,时间是:%s",asctime(localtime(&t)));
                   fclose(fp);
                 }
               }
}


程序如果成功执行,我们将在/tmp下,看到我们的日文件。

我们看到,我们编写的守护进程正在不断的想日志文件中输写我们自定义的日志内容。 
我们再来看一下进程列表,
注意黑线标出的部分,我们看到它的终端显示号为“?”,说明它已经脱离了终端的控制,成为我们系统的守护进程了! 

要想关闭它,我们只能使用强制关闭进程的命令:kill 进程号!例如,上例中,可使用:

kill 17694

或者关闭计算机。