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

守护进程(Daemon)

前言

Linux经常使用于服务器,程序一般不运行在前台。运行于前台的进程和终端关联,一旦终端关闭,进程也随之退出。由于守护进程不和终端关联,所以它的标准输出和标准输入也没法工做,调试信息应该写入到普通文件中,以便未来进行错误定位和调试。并且守护进程一般以root权限运行。编程

编程规则

  • 设置umask为0服务器

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

  • 调用setuid建立新会话socket

  • 从新设置但前目录函数

  • 关闭不须要的文件描述符性能

  • 重定向标准输入/标准输出/标准错误到/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、一、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);
    
            }
        }
    }

     

出错处理

因为不能再使用标准输入和输出,所以须要调用如下函数来输出调试信息。测试

Snip20161009_33

单例

守护程序每每只有一个实例,而不容许多个,能够用文件锁来实现单例。大数据

惯例

惯例是指你们都这么作,不这么作显得不专业的事情。ui

  • 单例文件路径在/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效率高  
使用位域来表示描述符集合 使用红黑树来保存文件集合

Snip20161023_1

Snip20161023_2

存储映射IO

 

 

10.1 前言

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

因为进程之间的虚拟地址没法相互访问,可是在实际的系统中,常常要涉及进程间的通讯,因此在Unix的发展中,人们创造了多种进程间通讯的方式,而这些通讯方式,都被Linux继承了过来。

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

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

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

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

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

10.2 匿名管道

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

 

Snip20161024_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);

Snip20161024_3

10.5 mmap实现共享内存

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

man 7 shm_overview

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

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

Snip20161024_4

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 信号量

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

 

Snip20161009_34

相关文章
相关标签/搜索