热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux基础守护进程、高级IO、进程间通信

守护进程(Daemon)前言Linux常用于服务器,程序通常不运行在前台。运行于前台的进程和终端关联,一旦终端关闭,进程也随之退出。因为守

守护进程(Daemon)

前言

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

编程规则

  • 设置umask为0

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

  • 调用setuid创建新会话

  • 重新设置但前目录

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

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

    #include
    #include

    #include

    #include

    #include

    #include

    int main()
    {pid_t pid
    &#61; fork();if(pid &#61;&#61; 0){pid &#61; fork();if(pid &#61;&#61; 0){// daemon processumask(0); // 设置掩码setsid(); // 让自己变成session leaderchdir("/"); // 修改当前目录chroot("/");// 获取最大的已经打开的文件描述符int maxfd &#61; 1024; // 演示// 把所有文件关闭int i;for(i&#61;0; i<&#61;maxfd; &#43;&#43;i){close(i);}// 重定向0、1、2文件到/dev/nullopen("/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);}}
    }

     

出错处理

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

单例

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

惯例

惯例是指大家都这么做&#xff0c;不这么做显得不专业的事情。

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

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

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

 

 

 

 

高级IO

前言

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

非阻塞IO

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

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

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

记录锁

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

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

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

9.4 IO多路转接

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

#include
#include

#include

#include
select.h>
#include

#include

#include
//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: 要求是集合中最大的文件描述符&#43;1
// fd_set* readfds: 想读取的文件描述符集合&#xff0c;这个参数既是输入&#xff0c;也是输出参数
// fd_set* writefds: 想写的文件描述符集合&#xff0c;一般为NULL
// fd_set* execptfds&#xff1a;出错&#xff0c;异常的文件描述符集合&#xff0c;一般为NULL
// struct timeval* timeout: 因为select是阻塞的调用&#xff0c;这个参数表示超过这个时间&#xff0c;无论文件描述符是否有消息&#xff0c;都继续往下执行
// 返回值&#xff1a;-1表示失败&#xff0c;0表示超时&#xff0c;而且没有任何的事件&#xff0c;大于0表示有事件的文件描述符的数量int main()
{
int fd_key;int fd_mice;fd_key &#61; open("/dev/input/event1", O_RDONLY);fd_mice &#61; 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中有两个文件描述符&#xff0c;分别是鼠标和键盘int nfds &#61; fd_key > fd_mice ? fd_key : fd_mice;nfds &#43;&#43;;struct timeval tv;tv.tv_sec &#61; 1; //tv.tv_usec &#61; 0; // 微秒 1/1000000 秒int ret;
RESELECT:ret
&#61; select(nfds, &set, NULL, NULL, &tv); // 阻塞一秒if(ret <0){if(errno &#61;&#61; EINTR) // 被中断打断
{// 补救goto RESELECT;}return 0;}if(ret &#61;&#61; 0){}if(ret > 0){// 用户动了鼠标或者键盘&#xff0c;从而鼠标文件描述符或者键盘文件描述符可读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的作用是&#xff0c;让内核监听一个fd集合&#xff0c;当集合中的fd有事件时&#xff0c;select会返回有消息的fd子集。

#include
#include

#include

#include
select.h>
#include

#include

#include
// fd_set最多能容纳1024个文件
//
// unsigned int data[32]; 32x32 &#61; 1024 int main()
{
int fd_key;int fd_mice;fd_key &#61; open("/dev/input/event1", O_RDONLY);fd_mice &#61; open("/dev/input/mice", O_RDONLY);int nfds &#61; fd_key > fd_mice ? fd_key : fd_mice;nfds &#43;&#43;;// 文件描述符集合的拷贝
fd_set set1;fd_set set2; // set1 --> set2memcpy(&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 &#61; 1; //tv.tv_usec &#61; 0; // 微秒 1/1000000 秒int ret &#61; select(nfds, &set, NULL, NULL, &tv);if(ret <0){if(errno &#61;&#61; EINTR)continue;return 0;}if(ret > 0){if(FD_ISSET(fd_key, &set)){// 既然鼠标有消息&#xff0c;就应该把数据都读出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差不多&#xff0c;但是操作接口完全不同。

 

#include
#include

#include

#include

#include

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

 

 select和epoll的区别

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

存储映射IO

 

 

10.1 前言

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

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

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

按照分类&#xff0c;进程间通信涉及三个方面&#xff1a;

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

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

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

10.2 匿名管道

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

 

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

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

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

10.2.1 创建

pipe函数
pipe函数产生两个文件描述符来表示管道两端&#xff08;读和写&#xff09;。
 

10.2.2 读写

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

10.2.3 应用

ps axu | grep a.out

单工&#xff1a;只能单方向通信
半双工&#xff1a;可以两个方向通信&#xff0c;但是同一时刻只能有一个方向通信
全双工&#xff1a;可以同时双方通信

10.3 命名管道

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

10.3.1 创建

mkfifo 创建管道文件

10.3.2 打开读端

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

10.3.3 打开写端

open("管道文件名", O_WRONLY);

#include
#include

#include

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

 

10.4 socketpair

socketpair和匿名管道类似&#xff0c;但是它是全双工的。

10.4.1 创建

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

10.5 mmap实现共享内存

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

man 7 shm_overview

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

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

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

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

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

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

shm_open&#xff1a;创建内存文件&#xff0c;路径要求类似/somename&#xff0c;以/起头&#xff0c;然后文件名&#xff0c;中间不能带/

10.6 文件锁

#include
#include

#include

#include

int main()
{
int fd &#61; open("a.txt", O_RDWR);// flock(fd, LOCK_SH); // 共享flock(fd, LOCK_EX); // 排他锁// 可以对文件进行读操作sleep(10);flock(fd, LOCK_UN); // 解锁
close(fd);
}

#include
#include

#include

#include

int main()
{
int fd &#61; open("a.txt", O_RDWR);// flock(fd, LOCK_EX); // 排他锁int ret &#61; flock(fd, LOCK_SH|LOCK_NB); // 共享锁if(ret &#61;&#61; 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
#include

#include

#include

#include

#include
int main()
{
int fd &#61; open("a.txt", O_RDWR);pid_t pid &#61; getpid();printf("process id is %d\n", (int)pid);// 锁文件开始位置的4K内容struct flock l;l.l_type &#61; F_WRLCK;l.l_whence &#61; SEEK_SET;l.l_start &#61; 0;l.l_len &#61; 4096;fcntl(fd, F_SETLKW, &l); // F_SETLKW&#xff1a;锁文件&#xff0c;如果锁不上&#xff08;原因&#xff1a;别人上锁了&#xff09;&#xff0c;就等
printf("get lock\n");sleep(10);// 解锁l.l_type &#61; F_UNLCK; fcntl(fd, F_SETLKW, &l);close(fd);
}

#include
#include

#include

#include

#include

#include
int main()
{
int fd &#61; open("a.txt", O_RDWR);// 锁文件开始位置的4K内容struct flock l;l.l_type &#61; F_WRLCK;l.l_whence &#61; SEEK_SET;l.l_start &#61; 1024;l.l_len &#61; 4096;fcntl(fd, F_GETLK, &l);printf("pid &#61; %d\n", (int)l.l_pid);#if 0fcntl(fd, F_SETLKW, &l); // F_SETLKW&#xff1a;锁文件&#xff0c;如果锁不上&#xff08;原因&#xff1a;别人上锁了&#xff09;&#xff0c;就等
printf("get lock\n");sleep(10);// 解锁l.l_type &#61; F_UNLCK; fcntl(fd, F_SETLKW, &l);
#endifclose(fd);
}

 

10.7 锁

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

10.8 信号量

信号量用于计数&#xff0c;而不用考虑进程竞争问题。

 

转:https://www.cnblogs.com/w-x-me/p/6412667.html



推荐阅读
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • Imtryingtofigureoutawaytogeneratetorrentfilesfromabucket,usingtheAWSSDKforGo.我正 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
author-avatar
逗壳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有