`
8366
  • 浏览: 799093 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

linux C语言系列--第六讲--进程

 
阅读更多

 

守护进程概述

守护进程,也就是通常所说的 Daemon 进程,是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导载入时启动,在系统关闭时终止。 Linux 有很多系统服务,大多数服务都是通过守护进程实现的,如本书在第二章中讲到的多种系统服务都是守护进程。同时,守护进程还能完成许多系统任务,例如,作业规划进程 crond 、打印进程 lqd 等(这里的结尾字母 d 就是 Daemon 的意思)。

由于在 Linux 中,每一个系统与用户进 行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关 闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才会退出。如果想让某个进程不因为用户、终端或者其他的变化而受到影响,那 么就必须把这个进程变成一个守护进程。可见,守护进程是非常重要的。

  编写守护进程

编写守护进程看似复杂,但实际上也是遵循一个特定的流程。只要将此流程掌握了,就能很方便地编写出用户自己的守护进程。下面就分 4 个步骤来讲解怎样创建一个简单的守护进程。在讲解的同时,会配合介绍与创建守护进程相关的几个系统函数,希望读者能很好地掌握。

1 .创建子进程,父进程退出

这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在 shell 终端里造成一种程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在 shell 终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离。

到这里,有心的读者可能会问,父进程创建了子进程之后退出,此时该子进程不就没有父进程了吗?守护进程中确实会出现这么一个有趣的现象,由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在 Linux 中,每当系统发现一个孤儿进程,就会自动由 1 号进程(也就是 init 进程)收养它,这样,原先的子进程就会变成 init 进程的子进程了。其关键代码如下所示:

pid = fork();

if (pid > 0)

{

    exit(0); /* 父进程退出 */

}

2 .在子进程中创建新会话

这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数 setsid() ,在具体介绍 setsid() 之前,读者首先要了解两个概念:进程组和会话期。

进程组。

进程组是一个或多个进程的集合。进程组由进程组 ID 来惟一标识。除了进程号( PID )之外,进程组 ID 也是一个进程的必备属性。

每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID 。且该进程 ID 不会因组长进程 的退出而受到影响。

会话期

 

1 setsid() 函数作用。

setsid() 函数用于创建一个新的会话,并担任该会话组的组长。调用 setsid() 有下面的 3 个作用。

  • 让进程摆脱原会话的控制。
  • 让进程摆脱原进程组的控制。
  • 让进程摆脱原控制终端的控制。

那么,在创建守护进程时为什么要调用 setsid() 函数呢?读者可以回忆一下创建守护进程的第一步,在那里调用了 fork() 函数来创建子进程再令父进程退出。由于在调用 fork() 函数时,子进程全盘复制了父进程的会话期、进程组和控制终端等,虽然父进程退出了,但原先的会话期、进程组和控制终端等并没有改变,因此,还不是真正意义上的独立,而 setsid() 函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

2 setsid() 函数格式

 

 

3 .改变当前目录为根目录

这一步也是必要的步骤。使用 fork() 创建的子进程继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统(比如“ /mnt/usb ”等)是不能卸载的,这对以后的使用会造成诸多的麻烦(比如系统由于某种原因要进入单用户模式)。因此,通常的做法是让“ / ”作为守护进程的当前工作目录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前工作目录换成其他的路径,如 /tmp 。改变工作目录的常见函数是 chdir()

4 .重设文件权限掩码

文件权限掩码是指屏蔽掉文件权限中的对应位。比如,有一个文件权限掩码是 050 ,它就屏蔽了文件组拥有者的可读与可执行权限。由于使用 fork() 函数新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。因此,把文件权限掩码设置为 0 ,可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask() 。在这里,通常的使用方法为 umask(0)

5 .关闭文件描述符

同文件权限掩码一样,用 fork() 函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法被卸载。

在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能达到守护进程,守护进程中用常规方法(如 printf() )输出的字符也不可能在终端上显示出来。所以,文件描述符为 0 1 2 3 个文件(常说的输入、输出和报错这 3 个文件)已经失去了存在的价值,也应被关闭。通常按如下方式关闭文件描述符:

 

for(i = 0; i < MAXFILE; i++)

{

      close(i);

}

 

一个标准 后台进程 daemon 的写法, 教科书式: daemon.c

 

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<fcntl.h>

#include<sys/types.h>

#include<unistd.h>

#include<sys/wait.h>

 

int main()

{

    pid_t pid;

    int   i, fd;

    char  *buf = "This is a Daemon\n";

   

    pid = fork(); /* 第一步 */

    if (pid < 0)

    {

         printf("Error fork\n");

         exit(1);

    }

    else if (pid > 0)

    {

        exit(0); /* 父进程推出 */

    }

   

    setsid(); /*第二步*/

    chdir("/"); /*第三步*/

    umask(0); /*第四步*/

    for(i = 0; i < getdtablesize(); i++) /*第五步*/

    {

        close(i);

    }

   

    /*这时创建完守护进程,以下开始正式进入守护进程工作*/

    while(1)

    {

         if ((fd = open("/tmp/daemon.log",

                         O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0)

         {

             printf("Open file error\n");

             exit(1);

         }

         write(fd, buf, strlen(buf) + 1);

         close(fd);

         sleep(10);

    }

    exit(0);

}
 

编译:

gcc -o daemon daemon.c

 

运行: ./daemon

 

$ tail -f /tmp/daemon.log

This is a Daemon

This is a Daemon

This is a Daemon

This is a Daemon

$ ps -ef|grep daemon

      76               root             1272   S   ./daemon

      85               root             1520   S   grep daemon

 

进程创建方式:

1. fork (see above sample)

2. exec ,6 个函数 分为 2个系列 C 系列 和L 系列

     C 系列: execv , execve, execvp

     L 系列 : execl , execle, execlp

L 是list(列表的意思), 表示函数需要将每个命令行的参数作为函数的参数进行传递

V是vector(矢量的意思),表示所有函数包装成一个矢量数组中传递

 

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[],char *env[])
{
    pid_t pid;
    char* para[]={"ls","-a",NULL};
    if((pid=fork())<0){
        perror("fork");
        exit(0);
    }
    if(pid==0){
        if(execl("/bin/ls","ls","-l",(char*)0)==-1){
            perror("fork");
            exit(0);
        }
    }
    if((pid=fork())<0){
        perror("fork");
        exit(0);
    }
    if(pid==0){
        if(execv("/bin/ls",para)==-1){
            perror("fork");
            exit(0);
        }
    }
    return 0;
}
 

 

3.system

 

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[],char *env[])
{
    printf("Call is return %d\n",system("ls -l"));
}
 

 

进程的终止:

 一个进程结束运行时,会向其父进程发送SIGCLD 信号, 父进程在接收到 SIGCLD 信号后可以忽略该信号或者安装信号处理函数处理该信号,通常情况下,父进程调用wait 函数 等待子进程退出,并获取子进程的返回码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syspes.h>
#include <sys/wait.h>
#include <signal.h>

void handle_sigcld(int signo){
    pid_t pid;
    int status;
    if((pid=wait(&status))!=-1)
    {
        printf("child %d process exit\n",pid);
    }
    if(WIFEXITED(status)){ // 判断子进程退出时是否有返回码
        printf("child process return code %d\n",WEXITSTATUS(status));
    }
    if(WIFSIGNALED(status)) // 判断子进程是否被信号中断而结束
    {
        printf("child process %d is interrupt by  signal\n",WTERMSIG(status));
    }
}
int main(int argc,char *argv[],char *env[])
{
    pid_t pid;
    signal(SIGCLD,handle_sigcld);
    if((pid=fork())<0)
    {
        perror("fork");
        exit(0);
    }
    if(pid==0)
    {
        sleep(2);
        exit(123);
    }
    sleep(5);
}

 [root@egovmo03 C]# ./a.out
child 3586 process exit
child process return code 123

分享到:
评论

相关推荐

    Linux下C语言编程教程

    第六章消息管理 …………………………………………………………………………39 第七章线程操作 …………………………………………………………………………49 第八章网络编程 …………………………………………...

    Linux 操作系统 C语言编程入门 PDF

    第六章 消息管理 …………………………………………………………………………39 第七章 线程操作 …………………………………………………………………………49 第八章 网络编程 …………………………………………...

    嵌入式Linux C语言应用程序设计

    程序代码\chapter06 文件夹中包含了第6章的示例源程序。其中pointer1.c是6.2.2的第一个源代码,pointer2.c是6.2.2的第二个源代码,pointer3.c是6.2.2的第三个源代码,pointer4.c是6.2.3的第一个源代码,pointer5.c是...

    用C语言模拟Linux操作系统下处理机调度实验报告

    1、每个进程用一个进程控制块PCB来代表,进程控制块包括进程名(进程的标识)、指针(把进程连成循环队列,用指针指出下一个进程的进程控制块首地址,最后一个进程中的指针指出第一个进程的进程控制块首地址)、已运行...

    嵌入式Linux C编程入门(第2版) PPT

    第6章 嵌入式linux c语言基础——数组、指针与结构 168 6.1 数组 169 6.1.1 一维数组 169 6.1.2 字符串 172 6.1.3 二维数组 174 6.2 指针 175 6.2.1 指针的概念 175 6.2.2 指针变量的操作 ...

    linux下的C语言编程入门

    共九个章节,第一章:基础知识 第二章:进程介绍 第三章:文件操作 第四章:时间概念 第五章:信号处理 第六章:消息管理 第七章:线程操作 第八章:网络编程 第九章:linux下C开发工具介绍 pdf文档

    linux C编程实战

     第6章 文件操作   第7章 进程控制   第8章 线程控制   第9章 信号及信号处理   第10章 进程间通信  第三篇 Linux网络和图形界面编程  第11章 网络编程   第12章 GTK+图形界面编程  第四篇 ...

    Linux操作系统下C语言编程入门

    第一章 基础知识 第二章 进程介绍 第三章 文件操作 第四章 时间概念 第五章 信号处理 第六章 消息管理 第七章 线程操作 第八章 网络编程 第九章 Linux 下 C 开发工具介绍

    Linux操作系统C语言编程(pdf)

    目 录 第一章 基础知识 第二章 进程介绍 第三章 文件操作 第四章 时间概念 第五章 信号处理 第六章 消息管理 第七章 线程操作 第八章 网络编程 第九章 Linux下 C 开发工具介绍

    嵌入式Linux应用程序开发标准教程(第2版全)

    第6章 文件I/O编程 6.1 Linux系统调用及用户编程接口(API) 6.1.1 系统调用 6.1.2 用户编程接口(API) 6.1.3 系统命令 6.2 Linux中文件及文件描述符概述 6.3 底层文件I/O操作 6.3.1 基本文件操作 6.3.2 文件锁 6.3.3 ...

    Linux操作系统下C语言编程入门.pdf

    第六章 消息管理 …………………………………………………………………………39 第七章 线程操作 …………………………………………………………………………49 第八章 网络编程 …………………………………………...

    嵌入式Linux应用程序开发详解(完整版)

    5.1 嵌入式开发环境的搭建 5.2 U-Boot移植 5.3 实验内容——移植Linux内核 本章小结 思考与练习 第6章 文件I/O编程 6.1 Linux系统调用及用户编程接口(API) 6.2 Linux中文件及文件描述符概述 6.3 不...

    Linux内核情景分析

    第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6. 5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 《LINUX内核源代码情景分析(下册)》图书...

    Linux操作系统下C语言编程快速入门

    第六章 消息管理 .................................................................................... 第七章 线程操作 ......................................................................................

    Linux内核情景分析(非扫描版)

    第6章 传统的Unix进程间通信 6.1 概述 6.2 管道和系统调用pipe() 6.3 命名管道 6.4 信号 6. 5 系统调用ptrace()和进程跟踪 6.6 报文传递 6.7 共享内存 6.8 信号量 《LINUX内核源代码情景分析(下册)》图书...

    精通LINUX下的C编程(配套光盘)第三部分

    第6章 进程间通信(IPC) 6.1 进程间通信机制概述 6.2 信号处理 6.3 管道 6.4 System V IPC机制 6.5 小结 习题 第7章 线程操作 7.1 线程概述 7.2 线程管理 7.3 小结 习题 第8章 网络编程 8.1 概述 8.2 ...

    精通LINUX下的C编程(配套光盘)第一部分

    第6章 进程间通信(IPC) 6.1 进程间通信机制概述 6.2 信号处理 6.3 管道 6.4 System V IPC机制 6.5 小结 习题 第7章 线程操作 7.1 线程概述 7.2 线程管理 7.3 小结 习题 第8章 网络编程 8.1 概述 8.2 ...

    Linux C程序设计大全

    第1篇 Linux下C语言基础 第1章 Linux简介 1.1 GNU简介 1.2 Linux简介 1.2.1 Linux发展史 1.2.2 Linux发行版 1.2.3 Linux内核版本 1.2.4 Linux与UNIX的关系 1.2.5 Linux在服务器方面的发展 1.2.6 Linux在嵌入式系统...

    精通LINUX下的C编程(配套光盘)第二部分

    第6章 进程间通信(IPC) 6.1 进程间通信机制概述 6.2 信号处理 6.3 管道 6.4 System V IPC机制 6.5 小结 习题 第7章 线程操作 7.1 线程概述 7.2 线程管理 7.3 小结 习题 第8章 网络编程 8.1 概述 8.2 ...

Global site tag (gtag.js) - Google Analytics