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

相關文章
相關標籤/搜索