unix环境高级编程(中)-进程篇

963 阅读25分钟

目录

前言

进程环境

进程控制

进程关系

信号

线程

线程控制

高级IO

进程间通信

网络间进程通信:套接字

高级进程间通信

前言

笔者将《unix环境高级编程》主要内容总结为三篇:文件篇进程篇高级io和进程间通信三大板块。本文是unix环境高级编程系列文章第二篇:进程篇。该篇主要包括:

进程环境

介绍进程相关的基本概念和使用环境:进程执行前的准备工作,进程如何终止,进程执行相关的环境变量表,进程执行时的内存空间布局,内存如何分配

进程控制

主要介绍进程控制符,进程如何创建,如何执行,如何终止,等待终止

进程关系

主要介绍进程之间的关系,包括:进程组,会话,控制终端。以及unix底层的数据结构如何建立他们之间的关系

信号

主要介绍信号的概念,如何设置信号处理函数,收到信号导致系统中断的调用以及能自动重启的调用。然后介绍如何发送信号,如何屏蔽信号,以及导致的信号阻塞

线程

主要介绍线程的概念,线程标识符,线程如何创建,如何终止,等待终止状态,设置自定义清理程序。然后对比了进程和线程相关概念和接口的对比。最后介绍线程的同步,包括:互斥量,读写锁,条件变量

线程控制

主要介绍线程属性,同步属性:互斥量属性,读写锁属性,条件变量属性。然后介绍如何创建线程私有数据。最后介绍信号和fork对线程的影响

守护进程

主要说明守护进程的特征,常见的系统守护进程,以及守护进程的编程规则。然后介绍处理守护进程的通用日志架构,最后介绍守护进程的一些惯例

一. 进程环境

1. 进程执行

进程执行从main函数开始,在这之前需要一些准备工作

  • 内核使用exec函数调用c程序
  • 执行c程序时,先调用一个特殊的启动例程。
  • 可执行文件将此启动例程指定为程序的起始地址(gcc设置)
  • 启动例程从内核取得命令行参数和环境变量
  • 上述工作准备就绪,开始执行main函数

2. 进程终止

2.1 正常终止

  • 从main返回
  • 调用exit:先执行一些清理工作(关闭io流等),然后进入内核
  • 调用_exit或_Exit:立即进入内核
  • 最后一个线程从其启动例程返回
  • 最后一个线程调用pthread_exit

2.2 异常终止

  • 调用abort
  • 接到一个信号并终止
  • 最后一个线程堆取消请求做出相应

2.3 终止处理程序

  • 终止处理程序由exit自动调用,无需手动调用
  • 注册终止处理程序的方法:atexit,参数为函数地址
  • 注册终止处理程序的最大数量:32
  • exit调用顺序:与注册顺序相反,且不会去重,登记多次就调用多次

2.4 c程序启动和终止流程图

  • 内核使用程序执行的唯一方法是:调用一个exec函数
  • 用户函数可以直接调用_exit或者_Exit终止程序,此时直接进入内核,不会调用终止处理程序
  • 如果调用exit终止程序,它会先调用注册的终止处理程序

3. 环境表

3.1 环境表内存布局

每个程序都会接收到一张环境表。环境表是一个字符指针数组,每个指针包含一个以null结束的c字符串地址。全局变量environ表示该地址

3.2 环境变量设置

  • 获取环境变量指定变量值的函数:getenv
  • 设置环境变量的函数:
    • putenv:参数为name=value的字符串形式,name存在则先删除
    • setenv:参数是否存在根据rewrite决定
    • unsetenv:删除某个环境变量

3.3 环境变量设置的底层实现

  • 环境变量表存放在进程存储空间的顶部(栈之上),见下一小节的存储空间布局
  • 删除一个环境变量很简单,找到后删除,后续指针依次往前移
  • 但是添加或者修改比较麻烦。因为它不能再向高地址(向上)扩展。同时也不能向下扩展到栈区。具体细节如下:
    • 修改现有的name:
      • 新value长度 < 旧的长度:覆盖写入
      • 新value长度 > 旧的长度:为value分配新空间,将指针指向该空间
    • 新增一个name:先为新name和value分配新空间
      • 第一次添加环境变量:先调用malloc为新的指针表分配空间,再将数据放到表尾
      • 不是第一次添加:调用realloc扩展空间

4. 存储空间布局

c程序由下面几部分组成:

  • 正文段:cpu执行的机器指令部分。正文段有可被共享,只读的特性。

  • 数据段(初始化数据段):包含程序中明确赋初始值的变量

  • 非初始化数据段(bss段):函数外申明的未初始化数据

  • 栈:局部变量,函数调用所需信息。每次调用时的返回地址等信息都存放在栈。栈从高地址向低地址方向增长

  • 堆:动态存储分配。位于非初始化数据段和栈之间

    使用size命令可以查看各个部分的大小

5. 存储器分配

5.1 内存空间动态分配的函数

  • malloc:分配制度字节数的存储区,初始值不确定
  • calloc:指定数量,指定长度的对象分配空间,每一位初始化为0
  • realloc:更改以前分配的长度
  • 最终都调用sbrk内核函数,分配后不释放会导致内存泄漏

5.2 其他替代的存储器分配程序

分配器出错难于追踪,很多替代的分配器在分配或释放时,会进行附加的操作,以便追踪问题

  • libmalloc:
  • vmalloc:
  • 快速适配quick-fit: 改善了标准malloc的最佳适配或首次适配分配策略
  • alloca:在栈上分配空间,而不是堆上。

二. 进程控制

1. 基本概念

1.1 进程标识符

  • 每个进程都有一个非负整数表示的唯一进程ID
  • id为0的进程通常是调度进程(交换进程,系统进程),是内核的一部分。
  • id为1的进程通常是init进程,是普通进程。以超级用户运行。文件为/sbin/init。负责在自举内核后启动unix系统。
  • id为2的进程通常为页守护进程,负责支持虚拟存储系统的分页操作
  • 除了进程id,每个进程还有其他标识,调用getpid可以获得

1.2 进程控制原语

  • 创建新进程:fork
  • 执行新进程:exec
  • 处理终止:exit
  • 等待终止:wait

2. 创建进程

2.1 fork函数

  • 一个现有进程调用fork可以创建一个新进程,称为子进程
  • fork函数调用一次,返回两次:子进程返回0,父进程返回子进程id
  • 子进程是父进程的副本。正文段由父子共享,但是数据空间,堆,栈各自维护
  • 由于fork之后常常跟随exec,现在很多实现并不是执行真正的复制,而是使用“写时复制”技术(COW):父子共享访问这些空间,且设为只读,如果试图修改,就只复制修改的部分
  • fork之后的执行顺序是不确定的,取决于内核使用的调度算法
  • fork的两个应用场景:
    • 网络服务:父进程接收客户端请求,请求来时fork出子进程处理,父进程继续等待请求
    • shell:一个进程执行不同的程序

2.2 vfork函数

功能类似与fork,区别如下:

  • 区别一:vfork创建的子进程并不将父进程的地址空间完全复制到子进程中,子进程调用exec时,它在父进程的空间中运行,以提高效率(比前面说的COW效率更高)
  • 区别二:保证子进程先执行,调用exec或exit之后父进程才可能被调度

3. 终止进程

前面介绍了终止进程的8中情况。不管哪种方式,都有一些特性:

  • 最后都会执行内核中的同一段代码:为进程关闭所有打开的文件描述符,释放使用的内存。
  • 都希望终止进程能够通知父进程它是如何终止的:
    • 正常终止:进程将退出状态作为参数传给函数
    • 异常终止:内核产生一个指示其终止原因的终止状态,_exit将终止状态转化为退出状态
  • 父进程都能通过wait或waitpid取得终止状态
  • 当一个进程中止时,内核就向其父进程发送SIGCHLD信号(异步信号)
  • 父进程可以选择忽略或提供信号处理程序
  • 如果父进程在子进程之前终止,子进程的父进程都变为init进程。(每个进程中止前都做检查)

4. 等待中止

4.1 wait/waitpid函数

4.1.1 调用wait的进程可能发生什么情况:

  • 如果所有子进程都还在运行,则阻塞
  • 如果一个子进程已经终止,正等待父进程获取终止状态,则取得状态立刻返回
  • 如果没有任何子进程,则出错返回
  • 如果进程由于收到SIGCHLD而调用wait,可能得到返回。任意时刻调用,可能会阻塞

4.1.2 区别

  • wait:使调用者阻塞
  • waitpid:选项可设置为阻塞或不阻塞,允许指定等待的子进程

4.1.3 参数

  • 不为空,则将状态信息保存在参数中返回
  • 终止状态宏:存放在<sys/wait.h>
    • WIFEXITED:正常终止
    • WIFSIGNALED:异常终止
    • WIFSTOPPED:暂停子进程
    • WIFCONTINUED:暂停之后继续

4.2 waitid函数

  • 功能与waitpid相似,不过使用单独的参数(idtype)表示要等待的子线程类型

4.3 wait3和wait4

  • 功能比前面几个wait函数多一项,与参数rusage有关
  • 要求返回终止进程及子进程使用的资源汇总,包括用户cpu时间总量,系统cpu时间总量,页面出错次数,接收到的信号次数。

5. 竞争条件

  • 多个进程企图对共享数据进行某些处理,而最后的结果取决与允许的顺序,则认为发生了竞争条件
  • 为了避免竞争条件,需要使用信号或进程间通信机制

6. 进程执行

6.1 exec说明

  • 进程调用exec以执行另一个程序
  • 调用exec时,该进程执行程序完全替换为新程序,新程序从main开始执行
  • 调用exec并不创建新的进程,所以前后进程id不变
  • exec用一个全新的程序替换当前进程的正文,数据,堆和栈

6.2 各函数说明

  • 前四个取路径名作为参数,后两个取文件名做为参数
  • filename中包含/符号,则将其视为路径名,否则就在PATH中搜索
  • l代表list,v表示vector。l要求每个参数单独传入,v要求传入参数数组
  • 以e结尾的函数可以传递环境字符串指针

7. 解释器文件

  • 在文本文件第一行添加 #! pathname,比如 #! /bin/bash
  • 这种文件由内核作为exec系统调用的一部分来完成
  • exec函数并不执行该文件,而是第一行pathname指定的文件

8. system函数

9. 进程会计

  • 启用该功能时,进程结束后会写一个会计记录:命令,cpu时间,启动时间等
  • 存放位置:/var/account
  • 头文件:<sys/acct.h>

三. 进程关系

1. 进程组

  • 每个进程除了有进程id外,还属于一个进程组(一个或多个进程的集合)
  • 进程组与同一个作业相关联,可以接收来自同一终端的各种信号
  • 进程组有一个唯一的id,相关函数:getpgrp,getpgid
  • 每个进程组都可以有一个组长进程(进程组id=进程id)
  • 加入或创建一个新的进程组:setpgid,setsid
  • 一个进程只能为它或它自己设置进程组ID,子进程调用exec之后就不能改变它都进程组id

2. 会话

  • 会话是一个或多个进程组的集合
  • 创建会话:setsid

3. 控制终端

  • 一个会话可以有一个控制终端
  • 通常是登陆的终端设备或伪终端设备
  • 一个会话中的几个进程组可以分为一个前台进程组和一个或多个后台进程组

4. 进程,进程组,会话,控制终端的实现

  • 每个会话都分配一个session结构
    • s_count:进程组数。减为0时,可释放该结构
    • s_leader:指向会话首进程指针,用proc结构表示
    • s_ttyvp:指向终端控制v-node的指针
    • s_ttyp:指向终端控制tty结构的指针
    • s_sid:会话id
  • 每个终端或伪终端设备都分配一个tty结构
    • t_session:指向会话(互相指向)
    • t_pgrp:指向前台进程组的pgrp结构。利用此发送信号到前台进程组
    • t_termios:包含终端有关的信息
    • t_winsize:包含终端窗口当前尺寸的winsize结构
  • 前台进程组pgrp,包含特定进程组信息
    • pg_id:进程组id
    • pg_session:指向此进程组所属的session
    • pg_members:进程组成员列表指针
  • 每个进程proc
    • p_pid:进程id
    • p_pptr:指向父进程的指针
    • p_pgrp:指向所属的进程组指针

四. 信号

1. 信号的概念

  • 信号是软件中断,提供了一种处理异步事件的方法
  • 每个信号都有一个名字,以SIG开头。在头文件<signal.h>中定义为正整数的宏
  • 产生信号的事件对进程而言是随机出现的,进程必须告诉内核调用什么信号处理函数或者忽略
  • 信号产生的一些举例
    • 硬件异常:如除0错误,无效内存引用
    • 进程调用kill(2):将信号发送给另一个进程或进程组,是否终止看信号类型,以及是否捕获该信号
    • 进程调用kill(1):将信号发送给另一个进程,是否终止看信号类型,以及是否捕获该信号
    • 检测到某种软件条件已经发生,发送信号通知其他进程
  • 一些常见的信号
    • SIGABORT:异常终止
    • SIGALRM:超时
    • SIGBUS:硬件故障
    • SIGCHLD:子进程状态改变
    • SIGEMT:硬件故障
    • SIGINT:终端中断符
    • SIGIO:异步IO
    • SIGKILL:终止
    • SIGPOLL:可轮询事件
    • SIGSEGV:无效内存引用
  • 信号的处理:
    • 执行一个程序时,通常所有信号的状态都是系统默认
    • 当调用exec时,将原先设置为要捕捉的信号都修改为默认(信号函数地址在新的进程可能无效)
    • shell中执行后台进程时,会忽略中断和退出信号
    • fork创建子进程时,复制父进程的存储映像,子进程会继承父进程的信号处理方式

2. signal函数

  • 作用:设置信号处理函数
  • 函数需要两个参数,返回一个函数指针,该指针指向的函数无返回值,返回的函数需要一个整形参数
  • 第一个参数signo是整数,第二个参数是函数指针,该指针需要一个整形参数,无返回值

3. 中断的系统调用

  • 进程执行低速的系统调用时,如果捕获到信号,系统调用被中断不再继续,返回出错。以便唤醒被阻塞的系统调用
  • 代表必须显示处理这种出错
  • 为了帮助应用程序每次手动处理这个情况,引入了系统调用的自动重启
  • 自动重启的系统调用包括
    • ioctl
    • read
    • readv
    • write
    • writev
    • wait
    • waitpid

4. 信号术语

  • 信号产生:引发信号的事件发生时
  • 信号来源:硬件异常,软件条件,终端信号,kill函数等
  • 信号递送:进程表中设置一个某种形式的标志
  • 信号未决:信号产生与信号递送之间的时间间隔
  • 信号阻塞:设置为阻塞时,将保持信号未决状态(传递被推迟,直到解除阻塞为止)。函数sigpending决定哪些信号设置为阻塞并处于未决状态。
  • 信号屏蔽:进程的信号屏蔽字,阻塞送到该进程的信号集:sigprocmask可以查看和更改信号屏蔽字
  • 信号集:sigset_t保存

5. kill和raise

  • kill:将信号发给进程或进程组
    • pid > 0: 信号发送给进程id为pid的进程
    • pid = 0:信号发送给与自己同处一个进程组的所有进程,而且有发送信号的权限
    • pid < 0:信号发送给与其他进程组id等于pid绝对值,而且有发送信号的权限。
    • pid = -1:发送信给号有权限发送的所有进程
  • raise:允许进程向自身发送信号
  • raise(signo) = kill(getpid(), signo)

6. alarm和pause

alarm

  • alarm:设置定时器,定时器超时时,产生SIGALRM信号。如果不忽略或不捕捉则停止调用该函数的进程
  • 参数:秒。
  • 说明:
    • 信号由内核产生,由于进程调度的延迟,得到控制和处理还需一些时间
    • 一个进程只能有一个闹钟,第二次设置会覆盖第一次,返回第一次剩余时间。如果参数为0即取消闹钟

pause

  • 使调用进程挂起,直至捕捉到一个信号
  • 只有执行了一个信号处理程序并返回,pause返回-1

7. 信号集

  • 概念:表示多个信号的数据类型
  • 相关函数:

8. 信号屏蔽字

  • 概念:规定了应该被阻塞不能传递给该进程的信号集,以保护不被信号中断的代码
  • 作用域:仅为单线程定义的
  • 函数:
  • how参数:
    • SIG_BLOCK:当前信号屏蔽字和set的并集。添加信号屏蔽字
    • SIG_UNBLOCK:当前信号屏蔽字和set补集的交集。解除信号屏蔽字
    • SIG_SETMASK:信号屏蔽字被set集合替代

9. sigspending

  • 作用:返回进程中被阻塞的信号集
  • 原型:

10. sigaction

  • 作用:检查或修改指定信号相关联的处理动作。替代早期的signal函数
  • 原型:
  • 参数:
    • act:若非空,为要修改的动作
    • oact:若非空,返回上一个动作

11. sigsuspend

  • 作用:恢复进程信号屏蔽字,使其休眠
  • 原型:
  • 参数:sigmask
    • 将进程的信号屏蔽字设置为由sigmask指定的值
  • 说明
    • 将进程的信号屏蔽字设置为由sigmask指定的值,在捕捉到一个信号或发生一个会终止该进程的信号前,该进程被挂起。如果捕捉到信号而且从函数返回,则suspend返回,且将信号屏蔽字还原

12. abort

  • 作用:使异常程序中止
  • 说明:发生SIGABORT信号给进程,进程不能忽略此信号。信号处理函数执行清理操作,具体由实现来决定,包括:清洗输出流,删除临时文件,关闭流等

13. sleep

  • 作用:是调用进程被挂起
  • 原型:
  • 被唤醒的情况
    • 时间超过参数的时间
    • 进程捕获到一个信号,并从信号处理函数返回

五. 线程

1. 线程的概念

  • 进程中独自处理任务的一个控制单元
  • 线程包含的信息:
    • 进程的所有资源,包括程序文本,程序全局内存,堆内存,栈,文件描述符
    • 自身的信息:线程id,寄存器值,栈,调度优先级和策略,信号屏蔽字,errorno变量以及线程私有数据
  • 多线程的好处:
    • 每种事件类型分配单独的线程,能简化异步事件代码。每个线程内部是同步的。
    • 要实现内存和文件描述符的共享,使用多进程是很复杂的。多线程天生具备该功能
    • 任务并行,提高吞吐率
    • 界面交互程序,使用多线程可以改善响应时间
  • 说明:即使在单核cpu上也是可以很好的使用多线程,并不是只有多核cpu才能使用多线程

2. 线程标识

  • 线程id:线程的唯一标识
  • 表示:pthread_t数据类型,各个操作系统有不同的具体实现,linux下为无符号的长整形
  • 线程id比较:不能用简单的数值比较,使用pthread_equal函数
  • 获取ID:pthread_self函数

3. 线程创建

  • 函数:pthread_create
  • 原型:
  • 参数:
    • tidp:返回的创建线程的线程id
    • attr:线程属性,设置为NULL表示使用默认线程属性
    • start_rin:线程执行入口函数
    • arg:线程执行函数的参数,多个参数必须以结构体的方式传入
  • 执行顺序:创建时并不能保证哪个线程会先执行

4. 线程中止

4.1 线程中止的情况

  • 进程中任意一个线程调用exit,_exit或_Exit中的任意一个都会使整个进程中止
  • 单个线程可以通过以下方式退出,而不用结束整个进程
    • 线程从启动例程中返回,返回值为线程退出码
    • 线程被同一进程的其他线程取消:pthread_cancel
    • 调用pthread_exit函数,参数为返回值

4.2 获取线程中止状态

  • 原型:
  • 说明:调用该函数的线程将阻塞,直到第一个参数指定的线程中止
  • 参数:
    • thread:
    • rval_ptr:
      • 如果线程处理函数通过return返回,该值为return的值
      • 如果线程通过pthread_exit返回,该值为返回的值
      • 如果线程被取消,该值为PTHRREAD_CANCELED
      • 如果该值自己设置为NULL,表示不想获取退出状态

4.3 设置线程清理处理程序

5. 进程原语和线程原语的对比

6. 线程同步

6.1 互斥量

概述

  • 本质是一把锁。访问共享资源前加锁,访问完成后释放锁。
  • 加锁后,其他线程想访问将会被阻塞直到锁被释放
  • 锁被释放时,所有被阻塞线程将变成可运行状态,但只有一个线程能抢到锁,其他线程再次被阻塞

相关接口

  • 数据类型:pthread_mutex_t
  • 初始化:
    • 静态分配的互斥量:置为常量PTHREAD_MUTEX_INITIALIZER
    • 动态分配的互斥量:pthread_mutex_init
  • 释放:
    • pthread_mutex_destroy
  • 加锁和释放锁
  • 注意事项:死锁

6.2 读写锁

概述

  • 也叫共享-独占锁
  • 运行比互斥量更高的并行性
  • 三种状态:
    • 读模式加锁状态:可多个线程占用
    • 写模式加锁状态:仅一个线程占用
    • 不加锁状态

相关接口

  • 数据类型:pthread_rwlock_t
  • 初始化和释放:
  • 加锁和解锁:

6.3 条件变量

概述

  • 给多个线程提供了一个汇合的场所
  • 与互斥量一起使用时,运行线程以无竞争的方式等待特定条件发生
  • 条件变量本身由互斥量保护

相关接口

  • 数据类型:pthread_cond_t
  • 初始化:
    • 静态分配的变量:PTHREAD_COND_INITIALIZER
    • 动态分配的变量:pthread_cond_init
  • 加锁和释放:
  • 唤醒等待条件的线程

六. 线程控制

1. 线程属性

  • 数据结构:pthread_attr_t,结构体内容不可见
  • 属性包括:
    • detashstate:线程的分离状态属性
    • guardsize:线程栈末尾的警戒缓冲区大小
    • stackaddr:线程栈最低地址
    • stacksize:线程栈大小
    • 并发度:控制着用户线程可以映射的内核线程或进程数目
  • 其他属性:不在pthread_attr_t中,影响调用pthread_cancel时的行为
    • 可取消状态:pthread_setcancelstate
      • PTHREAD_CANCEL_ENABLE
        • PTHREAD_CANCEL_DISABLE
    • 可取消类型
  • 初始化与释放:

2. 同步属性

2.1 互斥量属性

  • 数据结构:pthread_mutexattr_t
  • 初始化和释放:
  • 属性参数
    • 进程共享属性
      • PTHREAD_PROCESS_PRIVATE:默认属性,多个线程可访问同一个同步对象
      • PTHREAD_PROCESS_SHARED:多个进程共享的内存区域分配的互斥量可以用于进程同步
    • 类型属性
      • PTHREAD_MUTEX_NORMAL:正常属性,不做特殊的错误检查或死锁检查
      • PTHREAD_MUTEX_ERRORCHECK:提供错误检查
      • PTHREAD_MUTEX_RECURSIVE:运行进行多次加锁
      • PTHREAD_MUTEX_DEFAULT:请求默认语义,可以映射为其他类型

2.2 读写锁属性

  • 数据结构:pthread_rwlockattr_t
  • 初始化和释放:
  • 属性参数:
    • 进程共享属性:同互斥量属性

2.3 条件变量属性

  • 数据结构:pthread_condattr_t
  • 初始化和释放:
  • 属性参数:
    • 进程共享属性:同互斥量属性

3. 线程安全

  • 线程安全:一个函数在同一时间可以被多个线程安全的调用。或者,一个函数对多个线程来说是可重入的。

4. 线程私有数据

4.1 线程私有数据的分配-创建键

  • 创建与该数据关联的键,用于对线程私有数据对访问权
    • 第二个参数:为该键关联对析构函数,析构函数参数为地址
  • 该键可以被进程中对所有线程使用,但每个线程把这个键与不同的私有数据地址进行关联
  • 线程可以为线程私有数据分配多个键
  • 安全的创建键:调用pthread_once函数,将创建键的函数作为参数传入

4.2 键与线程私有数据的关联

4.2 键与线程私有数据的取消

5. 线程与信号

  • 每个信号有自己的信号屏蔽字,但是信号处理程序是共享的。意味着单个信号修改了某个信号相关的处理行为,其他线程必须共享这个行为
  • 设置线程信号屏蔽字:pthread_sigmask
  • 等待信号发生:sigwait
  • 发生信号到线程:pthread_kill
  • linux线程是以独立进程实现的。因此遇到信号时行为与其他系统不同

6. 线程与fork

  • 线程调用fork时,为子进程创建整个进程地址空间的副本,继承父进程的互斥量,读写锁和条件变量的状态
  • fork返回后,如果不是立马调用exec,需要清理锁状态:pthread_atfork函数可以做到
  • 子进程内部只包含一个线程副本:父进程中调用fork函数的线程

7. 线程与io

  • pread和pwrite作为原子操作,可以解决并发线程对同一文件进行读写操作对问题

七. 守护进程

1. 特征

  • 守护进程没有终端,在后台运行的一种生存期较长的进程
  • 大多数都以超级用户特权运行
  • 大多数守护进程的父进程是init进程

2. 系统守护进程

  • keventd守护进程: 在内核中运行计划执行函数提供进程上下文
  • kapmd守护进程:为计算机高级电源管理提供支持
  • kswapd守护进程:页面调出守护进程,将脏页低速写到磁盘以回收,用于支持虚拟子系统
  • bdflush和kupdated:将高速缓存到数据冲洗到磁盘上
  • portmap:端口映射守护进程
  • syslogd:记录日志消息
  • inetd,xinetd:监听系统网络接口,接收网络服务请求
  • nfsd,lockd,rpciod:提供网络文件系统的支持
  • crond:定时任务

3. 编程规则

  • 调用umask将文件模式创建屏蔽字设置为0
  • 调用fork,使父进程退出
  • 调用setsid,创建新的会话,使得新进程:
    • 成为新会话的首进程
    • 成为一个新进程组的组长进程
    • 没有控制终端
  • 将当前工作目录更改为根目录
  • 关闭不再需要的文件描述符
  • 某些守护进程打开/dev/null,使其具有文件描述符0,1,2

4. 出错记录

4.1 守护进程日志的来源

  • 内核例程调用log函数,任何一个用户进程通过打开然后读/dev/klog设备就可以读取这些信息
  • 大多数守护进程调用syslog(3)函数产生日志消息,这些消息发生至/dev/log(udp网络编程方式发送)
  • 在此主机上的用户进程,或通过网络连接的其他主机的用户进程可将日志消息发至UDP端口514

4.2 守护进程日志处理

  • syslogd守护进程读取这三种格式的日志文件。然后根据配置文件/etc/syslog.conf,决定将不同类型的日志送往何处

4.2 日志

5. 守护进程的惯例

  • 若守护进程使用锁文件(为了创建唯一守护进程),那么该文件通常放在/var/run/name.pid中
  • 若守护进程支持配置选项,配置文件通常放在/etc/name.conf
  • 守护进程可以用命令行启动,但通常是系统初始化脚本之一(/etc/rc*或/etc/init.d/*启动。要使守护进程重启,可在/etc/inittab中为该守护进程添加_respawn记录项
  • 配置文件启动后不再会被读取,为避免修改配置文件重启,某些守护进程会捕捉SIGHUP信号,然后重读配置文件