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); } } }
因爲不能再使用標準輸入和輸出,所以須要調用如下函數來輸出調試信息。測試
守護程序每每只有一個實例,而不容許多個,能夠用文件鎖來實現單例。大數據
慣例是指你們都這麼作,不這麼作顯得不專業的事情。ui
單例文件路徑在/var/run目錄下,內容爲該進程ID
配置文件應該在/etc目錄下
守護的啓動腳本一般放在/etc/init.d目錄下
在文件IO中,學習瞭如何經過read和write來實現文件的讀寫。在這一章討論一些高級的IO方式。
IO一般是阻塞的,好比讀鼠標文件,若是鼠標未產生數據,那麼讀操做會阻塞,一直到鼠標移動,才能返回。這種阻塞的IO簡化了程序設計,可是致使性能降低。
使用O_NONBLOCK標記打開文件,那麼read行爲就是非阻塞的了。若是read不到數據,read調用會返回-1,errno被標記爲EAGAIN。
若是open時沒有帶上O_NONBLOCK,那麼能夠經過fcntl設置這個模式。
若是多個進程/線程同時寫文件,那麼使用O_APPEND,能夠保證寫操做是原子操做,可是O_APPEND只寫到文件末尾。
若是須要修改文件內容,則沒法使用O_APPEND了,須要使用記錄鎖來鎖定文件,保證寫操做的原子性。
若是一個進程,同時要響應多路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"); // 鼠標有消息 } } }
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"); } } } }
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效率高 | |
使用位域來表示描述符集合 | 使用紅黑樹來保存文件集合 |
進程間通訊(IPC)方式有許多種。包括匿名管道、命名管道、socketpair、信號、信號量、鎖、文件鎖、共享內存等等。
因爲進程之間的虛擬地址沒法相互訪問,可是在實際的系統中,常常要涉及進程間的通訊,因此在Unix的發展中,人們創造了多種進程間通訊的方式,而這些通訊方式,都被Linux繼承了過來。
進程間通訊的原理,是在進程外的公共區域申請內存,而後雙方經過某種方式去訪問公共區域內存。
按照分類,進程間通訊涉及三個方面:
小數據量通訊(管道/socketpair)
大數據量通訊(共享內存)
進程間同步(socketpair/管道/鎖/文件鎖/信號量)
用於有親緣關係的進程間通訊,匿名管道是單工通訊方式。
內核的buffer究竟有多大?一個內存頁尺寸。實際在Ubuntu下測試是64K。當緩衝區滿的時候,write是阻塞的。
read管道時,若是管道中沒有數據,那麼阻塞等待。
read管道時,若是此時write端已經關閉,而此時管道有數據,就讀數據,若是沒有數據,那麼返回0表示文件末尾。
write管道時,若是此時全部的read端已經關閉,那麼內核會產生一個SIGPIPE給進程,SIGPIPE的默認會致使進程退出,若是此時進程處理了SIGPIPE信號,那麼write會返回-1,錯誤碼是EPIPE。
單工:只能單方向通訊
半雙工:能夠兩個方向通訊,可是同一時刻只能有一個方向通訊
全雙工:能夠同時雙方通訊
命名管道也是單工通訊,可是比匿名相比,它能夠用於非親緣關係的進程。
#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; } } } }
socketpair和匿名管道相似,可是它是全雙工的。
unix提供了一些內存共享機制,可是仍是習慣使用mmap進行內存共享。
有親緣的關係的父子進程,可使用匿名映射,直接將虛擬地址映射到內存。
若是進程之間沒有親緣關係,那麼就須要一個文件來進行內存共享。
可是若是使用了硬盤文件,那麼效率相對底下。最好使用內存文件來映射,效率更加高。
shm_open:建立內存文件,路徑要求相似/somename
,以/
起頭,而後文件名,中間不能帶/
。
#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); }
pthread_mutex_init的鎖,能夠用於進程間同步,可是要求鎖變量在共享內存中。
信號量用於計數,而不用考慮進程競爭問題。