Linux基础守护进程、高级IO、进程间通信
守护进程(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了,需要使用记录锁来锁定文件,保证写操作的原子性。
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 创建
10.2.2 读写
10.2.3 应用
单工:只能单方向通信
半双工:可以两个方向通信,但是同一时刻只能有一个方向通信
全双工:可以同时双方通信
10.3 命名管道
命名管道也是单工通信,但是比匿名相比,它可以用于非亲缘关系的进程。
10.3.1 创建
10.3.2 打开读端
10.3.3 打开写端
#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 创建
10.5 mmap实现共享内存
unix提供了一些内存共享机制,但是还是习惯使用mmap进行内存共享。
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 信号量
信号量用于计数,而不用考虑进程竞争问题。
最新文章
- [转]DDD领域驱动设计基本理论知识总结
- PAT 1038. 统计同成绩学生(20)
- TortoiseGit流程安装使用手册
- 如何在两个activity之间传递bitmap
- Windows 自动关机/定时关机 命令 shuntdown
- 《精通移动app测试实战:技术、工具和案例》新书上市
- Ado Recordset.open
- requirejs+anjularjs+express框架
- nRF51800 蓝牙学习 进程记录 1:感想
- JavaScript 判断是PC端还是移动端
- 对低开销的静态组件使用v-once
- TCP/IP学习20180709-数据链路层-arp协议
- Sliverlight常见错误集锦
- constructor&;object 的对比
- 软件工程 week 05
- jQuery之制作简单的轮播图效果
- [Leetcode 739]*还有几天会升温 Daily Temperatures
- 遇到Elements in iteration expect to have &#39;v-bind:key&#39; directives.&#39; 这个错误
- python实现测试报告的bug统计
- svn 未提交的显示黑色的星*
热门文章
- 关于HM NISEDIT在新版系统下编译并运行提示权限不足问题的解决方案
- Linux命令全解
- PHP全栈开发(八):CSS Ⅴ 超链接 style
- 2022-08-21-Freewind主题_cdn替换版
- 16.MongoDB系列之分片管理
- .NET性能系列文章一:.NET7的性能改进
- 前端JS获取路由地址里的参数QueryString取值
- 你给文字描述,AI艺术作画,精美无比!附源码,快来试试!
- 【Bluetooth蓝牙开发】一、开篇词 | 打造全网最详细的Bluetooth开发教程
- SpringBoot&;MyBatisPlus