守护进程(Daemon)

前言

Linux常用于服务器,程序通常不运行在前台。运行于前台的进程和终端关联,一旦终端关闭,进程也随之退出。因为守护进程不和终端关联,因此它的标准输出和标准输入也无法工作,调试信息应该写入到普通文件中,以便将来进行错误定位和调试。而且守护进程通常以root权限运行。

编程规则

  • 设置umask为0

  • 调用fork,并让父进程退出

  • 调用setuid创建新会话

  • 重新设置但前目录

  • 关闭不需要的文件描述符

  • 重定向标准输入/标准输出/标准错误到/dev/null

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <syslog.h>
    int main()
    {
    pid_t pid = fork();
    if(pid == 0)
    {
    pid = fork();
    if(pid == 0)
    {
    // daemon process
    umask(0); // 设置掩码
    setsid(); // 让自己变成session leader
    chdir("/"); // 修改当前目录
    chroot("/"); // 获取最大的已经打开的文件描述符
    int maxfd = 1024; // 演示
    // 把所有文件关闭
    int i;
    for(i=0; i<=maxfd; ++i)
    {
    close(i);
    } // 重定向0、1、2文件到/dev/null
    open("/dev/null", O_RDONLY); // 标准输入
    open("/dev/null", O_WRONLY); // 标准输出
    open("/dev/null", O_WRONLY); // 标准错误 // printf(""); // --> aaa.txt 效率低下
    //
    syslog(LOG_ERR|LOG_KERN, "haha, this is syslog....\n"); // 后台进程不退出
    while(1)
    sleep(1); }
    }
    }

出错处理

由于不能再使用标准输入和输出,因此需要调用以下函数来输出调试信息。

单例

守护程序往往只有一个实例,而不允许多个,可以用文件锁来实现单例。

惯例

惯例是指大家都这么做,不这么做显得不专业的事情。

  • 单例文件路径在/var/run目录下,内容为该进程ID

  • 配置文件应该在/etc目录下

  • 守护的启动脚本通常放在/etc/init.d目录下

高级IO

前言

在文件IO中,学习了如何通过read和write来实现文件的读写。在这一章讨论一些高级的IO方式。

非阻塞IO

IO通常是阻塞的,比如读鼠标文件,如果鼠标未产生数据,那么读操作会阻塞,一直到鼠标移动,才能返回。这种阻塞的IO简化了程序设计,但是导致性能下降。

使用O_NONBLOCK标记打开文件,那么read行为就是非阻塞的了。如果read不到数据,read调用会返回-1,errno被标记为EAGAIN。

如果open时没有带上O_NONBLOCK,那么可以通过fcntl设置这个模式。

记录锁

如果多个进程/线程同时写文件,那么使用O_APPEND,可以保证写操作是原子操作,但是O_APPEND只写到文件末尾。

如果需要修改文件内容,则无法使用O_APPEND了,需要使用记录锁来锁定文件,保证写操作的原子性。

#include "../h.h"
 
int main()
{
    int fd = open("a.txt", O_RDWR);
 
    // lock it
    struct flock l;
    l.l_type = F_WRLCK;
    l.l_whence = SEEK_SET;
    l.l_start = 0;
    l.l_len = 128;
 
    int ret = fcntl(fd, F_SETLKW, &l);
    if(ret == 0)
    {
        printf("lock success\n");
    }
    else
    {
        printf("lock failure\n");
    }
 
    getchar();
    l.l_type = F_UNLCK;
    fcntl(fd, F_SETLKW, &l);
 
}

9.4 IO多路转接

如果一个进程,同时要响应多路IO数据,那么这个程序设计将会很麻烦。一般程序都是需要响应多路IO的,比如GUI程序都需要处理鼠标和键盘文件。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h> //void FD_CLR(int fd, fd_set *set);
// 将fd从set中拿掉
//
//int FD_ISSET(int fd, fd_set *set);
//判断fd是否在集合中
//
//void FD_SET(int fd, fd_set *set);
//将fd加入到集合中
//
//void FD_ZERO(fd_set *set);
//将集合清空 // int select(int nfds, fd_set *readfds, fd_set *writefds,
// fd_set *exceptfds, struct timeval *timeout);
// int nfds: 要求是集合中最大的文件描述符+1
// fd_set* readfds: 想读取的文件描述符集合,这个参数既是输入,也是输出参数
// fd_set* writefds: 想写的文件描述符集合,一般为NULL
// fd_set* execptfds:出错,异常的文件描述符集合,一般为NULL
// struct timeval* timeout: 因为select是阻塞的调用,这个参数表示超过这个时间,无论文件描述符是否有消息,都继续往下执行
// 返回值:-1表示失败,0表示超时,而且没有任何的事件,大于0表示有事件的文件描述符的数量 int main()
{
int fd_key;
int fd_mice; fd_key = open("/dev/input/event1", O_RDONLY);
fd_mice = open("/dev/input/mice", O_RDONLY);
if(fd_key < 0 || fd_mice < 0)
{
perror("open key mice");
return 0;
} // fd_set 文件描述符集合类型
fd_set set;
FD_ZERO(&set);
FD_SET(fd_key, &set);
FD_SET(fd_mice, &set); // 此时set中有两个文件描述符,分别是鼠标和键盘
int nfds = fd_key > fd_mice ? fd_key : fd_mice;
nfds ++; struct timeval tv;
tv.tv_sec = 1; // 秒
tv.tv_usec = 0; // 微秒 1/1000000 秒
int ret;
RESELECT:
ret = select(nfds, &set, NULL, NULL, &tv); // 阻塞一秒 if(ret < 0)
{
if(errno == EINTR) // 被中断打断
{
// 补救
goto RESELECT;
}
return 0;
} if(ret == 0)
{ } if(ret > 0)
{
// 用户动了鼠标或者键盘,从而鼠标文件描述符或者键盘文件描述符可读
if(FD_ISSET(fd_key, &set))
{
printf("keyboard message\n");
// 键盘有消息
}
if(FD_ISSET(fd_mice, &set))
{
printf("mice message\n");
// 鼠标有消息
}
}
}
 

9.4.1 select

select的作用是,让内核监听一个fd集合,当集合中的fd有事件时,select会返回有消息的fd子集。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h> // fd_set最多能容纳1024个文件
//
// unsigned int data[32]; 32x32 = 1024 int main()
{
int fd_key;
int fd_mice; fd_key = open("/dev/input/event1", O_RDONLY);
fd_mice = open("/dev/input/mice", O_RDONLY); int nfds = fd_key > fd_mice ? fd_key : fd_mice;
nfds ++; // 文件描述符集合的拷贝
fd_set set1;
fd_set set2; // set1 --> set2
memcpy(&set2, &set1, sizeof(set1)); while(1)
{
fd_set set;
FD_ZERO(&set);
FD_SET(fd_key, &set);
FD_SET(fd_mice, &set); struct timeval tv;
tv.tv_sec = 1; // 秒
tv.tv_usec = 0; // 微秒 1/1000000 秒 int ret = select(nfds, &set, NULL, NULL, &tv);
if(ret < 0)
{
if(errno == EINTR)
continue;
return 0;
} if(ret > 0)
{
if(FD_ISSET(fd_key, &set))
{
// 既然鼠标有消息,就应该把数据都读出
char buf[1024];
read(fd_key, buf, sizeof(buf));
printf("key event\n");
}
if(FD_ISSET(fd_mice, &set))
{
char buf[1024];
read(fd_mice, buf, sizeof(buf));
printf("mice event\n");
}
}
}
}

9.4.2 epoll

epoll的作用和select差不多,但是操作接口完全不同。

 
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <fcntl.h> // 通过epoll来实现多路io复用
int main()
{
int fd_key = open("/dev/input/event1", O_RDONLY);
int fd_mice = open("/dev/input/mice", O_RDONLY); if(fd_key < 0 || fd_mice < 0)
{
perror("open mice and keyboard");
return -1;
} // 创建epoll对象,创建epoll的参数已经废弃了,随便填
int epollfd = epoll_create(512);
if(epollfd < 0)
{
perror("epoll");
return -1;
} // 把鼠标和键盘的文件描述符,加入到epoll集合中
struct epoll_event ev;
ev.data.fd = fd_key; // 联合体,这个联合体用来保存和这个文件描述符相关的一些数据,用于将来通知时,寻找文件描述符
ev.events = EPOLLIN | EPOLLONESHOT; // epoll要监听的事件,读或者写
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_key, &ev); ev.data.fd = fd_mice;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd_mice, &ev);
// 调用epoll_ctl时,第四个参数被epoll_ctl拷贝走 struct epoll_event ev_out[2]; while(1)
{
int ret = epoll_wait(epollfd, ev_out, 2, 2000);
if(ret < 0)
{
if(errno == EINTR)
continue;
return -2;
} if(ret > 0)
{
int i;
for(i=0; i<ret; ++i)
{
if(ev_out[i].data.fd == fd_mice)
{
// 鼠标有消息
// char buf[1024];
// read(fd_mice, buf, sizeof(buf));
printf("mice\n");
}
else if(ev_out[i].data.fd == fd_key)
{
// char buf[1024];
// read(fd_key, buf, sizeof(buf));
printf("key\n");
}
}
}
}
}

select和epoll的区别

select epoll
出现早
大规模文件描述符效率低 大规模文件描述符效率高
小规模是select效率高  
使用位域来表示描述符集合 使用红黑树来保存文件集合

存储映射IO

10.1 前言

进程间通信(IPC)方式有许多种。包括匿名管道、命名管道、socketpair、信号、信号量、锁、文件锁、共享内存等等。

由于进程之间的虚拟地址无法相互访问,但是在实际的系统中,经常要涉及进程间的通信,所以在Unix的发展中,人们创造了多种进程间通信的方式,而这些通信方式,都被Linux继承了过来。

进程间通信的原理,是在进程外的公共区域申请内存,然后双方通过某种方式去访问公共区域内存。

按照分类,进程间通信涉及三个方面:

  • 小数据量通信(管道/socketpair)

  • 大数据量通信(共享内存)

  • 进程间同步(socketpair/管道/锁/文件锁/信号量)

10.2 匿名管道

用于有亲缘关系的进程间通信,匿名管道是单工通信方式。

内核的buffer究竟有多大?一个内存页尺寸。实际在Ubuntu下测试是64K。当缓冲区满的时候,write是阻塞的。

read管道时,如果管道中没有数据,那么阻塞等待。
read管道时,如果此时write端已经关闭,而此时管道有数据,就读数据,如果没有数据,那么返回0表示文件末尾。

write管道时,如果此时所有的read端已经关闭,那么内核会产生一个SIGPIPE给进程,SIGPIPE的默认会导致进程退出,如果此时进程处理了SIGPIPE信号,那么write会返回-1,错误码是EPIPE。

10.2.1 创建

pipe函数
pipe函数产生两个文件描述符来表示管道两端(读和写)。
 

10.2.2 读写

read:
1. 如果管道有数据,读数据
2. 如果管道没有数据
   此时写端已经关闭,返回0
     如果写端没有关闭,阻塞等待
 
write:
1. 如果管道有空间,写数据,写入的数据长度依赖管道的buffer剩余的空间。如果剩余空间>=写入长度,那么数据全部写入,如果剩余空间<写入长度,那么写入剩余空间长度,并且write立即返回,返回值为写入的长度。
2. 如果管道没有剩余空间,那么阻塞。
3. 如果write时,读端已经关闭,那么程序产生一个SIGPIPE信号,导致程序终止。如果程序有处理SIGPIPE信号,那么程序不会终止,此时write返回-1,错误码标记为EPIPE。

10.2.3 应用

ps axu | grep a.out

单工:只能单方向通信
半双工:可以两个方向通信,但是同一时刻只能有一个方向通信
全双工:可以同时双方通信

10.3 命名管道

命名管道也是单工通信,但是比匿名相比,它可以用于非亲缘关系的进程。

10.3.1 创建

mkfifo 创建管道文件

10.3.2 打开读端

open("管道文件名",O_RDONLY);
如果此时没有其他进程打开写端,那么该open阻塞
 

10.3.3 打开写端

open("管道文件名", O_WRONLY);
 
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h> int main()
{
// 打开文件时,添加非阻塞属性
//int fd = open("/dev/input/mice", O_RDONLY | O_NONBLOCK); // 先打开文件,再通过fcntl设置O_NONBLOCK属性
int fd = open("/dev/input/mice", O_RDONLY); int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags); while(1)
{
char buf[1024];
int ret = read(fd, buf, sizeof(buf));
if(ret == -1) // 错误发生
{
if(errno == EAGAIN || errno == EWOULDBLOCK) // EAGAIN错误码表示:底层没有数据,应该继续再尝试读 EWOULDBLOCK
{
//鼠标并没有移动,底层并没有数据可以读,这种不算真的错误
printf("mouse not move\n");
}
else // 真的有错误发生了
{
return -1;
}
}
}
}

10.4 socketpair

socketpair和匿名管道类似,但是它是全双工的。

10.4.1 创建

int fd[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, fd);

10.5 mmap实现共享内存

unix提供了一些内存共享机制,但是还是习惯使用mmap进行内存共享。

man 7 shm_overview

10.5.1 有亲缘关系的进程之间mmap共享

有亲缘的关系的父子进程,可以使用匿名映射,直接将虚拟地址映射到内存。

10.5.2 无亲缘关系的进程之间mmap共享

如果进程之间没有亲缘关系,那么就需要一个文件来进行内存共享。

但是如果使用了硬盘文件,那么效率相对底下。最好使用内存文件来映射,效率更加高。

10.5.3 使用shm_open打开共享内存文件

shm_open:创建内存文件,路径要求类似/somename,以/起头,然后文件名,中间不能带/

10.6 文件锁

#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <stdio.h>
int main()
{
int fd = open("a.txt", O_RDWR); // flock(fd, LOCK_SH); // 共享
flock(fd, LOCK_EX); // 排他锁 // 可以对文件进行读操作
sleep(10); flock(fd, LOCK_UN); // 解锁 close(fd);
}
#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h>
#include <stdio.h>
int main()
{
int fd = open("a.txt", O_RDWR); // flock(fd, LOCK_EX); // 排他锁
int ret = flock(fd, LOCK_SH|LOCK_NB); // 共享锁
if(ret == 0)
{ printf("get lock\n");
// flock(fd, LOCK_EX); // 排他锁 // 可以对文件进行读操作
sleep(1); flock(fd, LOCK_UN); // 解锁
}
else
{
printf("can not get lock\n");
} close(fd);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h> int main()
{
int fd = open("a.txt", O_RDWR);
pid_t pid = getpid();
printf("process id is %d\n", (int)pid); // 锁文件开始位置的4K内容
struct flock l;
l.l_type = F_WRLCK;
l.l_whence = SEEK_SET;
l.l_start = 0;
l.l_len = 4096;
fcntl(fd, F_SETLKW, &l); // F_SETLKW:锁文件,如果锁不上(原因:别人上锁了),就等 printf("get lock\n"); sleep(10); // 解锁
l.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &l); close(fd);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/file.h> int main()
{
int fd = open("a.txt", O_RDWR); // 锁文件开始位置的4K内容
struct flock l;
l.l_type = F_WRLCK;
l.l_whence = SEEK_SET;
l.l_start = 1024;
l.l_len = 4096; fcntl(fd, F_GETLK, &l); printf("pid = %d\n", (int)l.l_pid); #if 0
fcntl(fd, F_SETLKW, &l); // F_SETLKW:锁文件,如果锁不上(原因:别人上锁了),就等 printf("get lock\n"); sleep(10); // 解锁
l.l_type = F_UNLCK;
fcntl(fd, F_SETLKW, &l);
#endif
close(fd);
}

10.7 锁

pthread_mutex_init的锁,可以用于进程间同步,但是要求锁变量在共享内存中。

10.8 信号量

信号量用于计数,而不用考虑进程竞争问题。

Linux基础守护进程、高级IO、进程间通信的更多相关文章

  1. python实现Linux启动守护进程

    python实现Linux启动守护进程 DaemonClass.py代码: #/usr/bin/env python # -*- coding: utf-8 -*- import sys import ...

  2. linux C守护进程编写

    linux编程-守护进程编写 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待 处理某些发生的事件.守护进程是一种很有用的进程. Linux的大多数服 ...

  3. linux 创建守护进程的相关知识

    linux 创建守护进程的相关知识 http://www.114390.com/article/46410.htm linux 创建守护进程的相关知识,这篇文章主要介绍了linux 创建守护进程的相关 ...

  4. asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二)

    原文:asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二) 续上一篇文章:asp.net core2.0 部署centos7/linux系统 -- ...

  5. 【Linux】- 守护进程的启动方法

    转自:Linux 守护进程的启动方法 Linux中"守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问 ...

  6. 深入理解Linux操作系统守护进程的意义

    Linux服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程(daemons)来执行的.守护进程 ...

  7. linux下守护进程的创建

    最近在学习linux c编程 看到了守护进程的创建,感觉很好玩, 测试环境ubuntu 15.04 下面贴出测试代码 #include <stdio.h> #include <std ...

  8. hadoop不能互相访问和linux防火墙守护进程

    前言——作为装过几次集群的菜鸟,对于hadoop集群的安装还是比较有心得的:只要配置文件够好,集群配置就非常容易,否则也容易出现莫名其妙的问题!总结了一份3台机器搭建较完好的集群的一份配置文件. 在我 ...

  9. Linux Supervisor 守护进程基本配置

    supervisor:C/S架构的进程控制系统,可使用户在类UNIX系统中监控.管理进程.常用于管理与某个用户或项目相关的进程. 组成部分supervisord:服务守护进程supervisorctl ...

  10. Linux之守护进程

    一.守护进程概述 在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进 程.为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一 ...

随机推荐

  1. SpringBoot的starter到底是什么?

    前言 我们都知道,Spring的功能非常强大,但也有些弊端.比如:我们需要手动去配置大量的参数,没有默认值,需要我们管理大量的jar包和它们的依赖. 为了提升Spring项目的开发效率,简化一些配置, ...

  2. ARC148游记

    A - mod M 题目链接 这道题我们可以首先对于所有的数 $%2$ ,可以证明出答案最多不超过 $2$ ,此时我们就可以把问题转化为:是否存在一个数使得序列 $a$ 中所有元素减去这个数之后的最大 ...

  3. 220722 T1 分树 (模拟)

    dfs一遍求出以每个节点为根的子树大小,然后枚举n的约数,对于每个约数i,统计sz[ ]是i的倍数的有多少个(开桶统计),如果有n/i个则答案+1. 这道题也就是个结论题,画图分析一下.复杂度O(n* ...

  4. SpringMvc(五) - 支付宝沙箱和关键字过滤,md5加密,SSM项目重要知识点

    1.支付宝沙箱 1.1 jar包 alipay-sdk <!-- alipay-sdk --> <dependency> <groupId>com.alipay.s ...

  5. Docker | 使用dockerfile生成镜像,清理docker空间

    用dockerfile生成镜像并挂载数据卷 编写dockerfile文件 创建dockerfile01 文件 # 基础镜像 FROM centos VOLUME ["volume01&quo ...

  6. Vue学习之--------Vue生命周期beforeCreate、created、beforeMount、mounted、beforeDestroy 。。。(图解详细过程)(2022/7/17)

    文章目录 1.Vue生命周期 1.1 概念 1.2 图解 2.钩子函数的用法说明 2.1 beforeCreate()和created()的详细讲述 2.1.1 方法说明 2.1.2 代码实例 2.1 ...

  7. mybatisPlus在Springboot中的使用

    文章目录 1.简介 2.支持的数据库 3.框架 4.创建一个springboot项目 4.1 .pom文件中加入依赖 4.2.yml文件的配置 4.3 .数据库脚本 4.4.实体类 4.5 .启动类添 ...

  8. Java函数式编程:一、函数式接口,lambda表达式和方法引用

    Java函数式编程 什么是函数式编程 通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此我们会得到更加可靠的代码,并获得更高的效率 我们可以这样理解:面向对象编程抽象数据,函数式编程抽象 ...

  9. 【vue2】Style和Class,条件,列表渲染,双向数据绑定,事件处理

    目录 1.style和class 2. 条件渲染 2.1 指令 2.2 案例 3. 列表渲染 3.1 v-for:放在标签上,可以循环显示多个此标签 3.2 v-for 循环数组,循环字符串,数字,对 ...

  10. 实战进阶 Vue3+Axios+pinia

    实战进阶 Vue3+Axios+pinia 创建文件utils/request.js import Axios from 'axios'; export const request = Axios.c ...