非阻塞I/O
對低速設備的I/O操作可能會使進程永久阻塞,這類系統調用主要有如下情況:
(1)如果數據並不存在,則讀文件可能會使調用者永遠阻塞(例如讀管道、終端設備和網絡設備)。
(2)如果數據不能立即被接受,則寫這些同樣的文件也會使調用者永遠阻塞;
(3)在某些條件發生之前,打開文件會被阻塞(例如以只寫方式打開一個FIFO,那麼在沒有其他進程已用讀方式打開該FIFO時);
(4)對已經加上強制性鎖的文件進行讀、寫;
(5)某些ioctl操作;
(6)某些進程間通信函數;
非阻塞I/O調用open、read和write等I/O操作函數使上述的慢速系統調用在不能立即完成的情況下,立即出錯返回。
對一個給定的描述符有兩種方法設置其爲非阻塞:
(1)如果是調用open以獲得該描述符,則可指定O_NONBLOCK標誌;
(2)對於已經打開的一個描述符,則可調用fcntl打開O_NONBLOCK文件狀態標誌(注意:設置文件狀態標誌的方法)。
記錄鎖(字節範圍鎖)
功能:當第一個進程正在讀或修改文件的某個部分時,使用記錄鎖可以阻止其他進程修改同一文件區。記錄鎖鎖定的是文件中的一個區域(也可能是整個文件)。
函數原型
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *flockptr*/)
struct flock
{
short int l_type; /* Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK. */
short int l_whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* offset in bytes, relative to l_whence */
off_t l_len; /* length in bytes; 0 means lock to EOF. */
pid_t l_pid; /* Process holding the lock. */
};
函數lock_test
#include "apue.h"
#include <fcntl.h>
pid_t
lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type; /* F_RDLCK or F_WRLCK */
lock.l_start = offset; /* byte offset, relative to l_whence */
lock.l_whence = whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
lock.l_len = len; /* #bytes (0 means to EOF) */
if (fcntl(fd, F_GETLK, &lock) < 0)
err_sys("fcntl error");
if (lock.l_type == F_UNLCK)
return(0); /* false, region isn't locked by another proc */
return(lock.l_pid); /* true, return pid of lock owner */
}
進程不能用lock_test來測試自己是否在文件的某一部分持有一把鎖,F_GETLK命令的定義說明,返回信息指示是否有現有的鎖阻止調用進程設置它自己的鎖。因爲 F_SETLK 和 F_SETLKW 總是替換調用進程現有的鎖(若已存在),所以調用進程決不會阻塞在自己持有的鎖上,所以F_GETLK 命令決不會報告調用進程自己持有的鎖。
鎖的隱含繼承與釋放
(1)鎖與進程和文件相關聯:當一個進程終止時,它所建立的鎖全部釋放。當文件描述符關閉時,則該文件描述符上由某個進程設置的鎖也會釋放
(2)由fork產生的子進程不繼承父進程所設置的鎖
(3)在執行exec後,新程序可以繼承原執行程序的鎖,但如果一個文件描述符設置了執行時關閉標誌,那麼當作爲exec的一部分關閉該文件描述符時,將釋放響應文件的所有鎖
I/O多路轉接
概述:將我們感興趣的描述符列表傳給一個函數,該函數直到這些描述符中的一個已經準備好I/O時才返回。
select 和 poll 函數基本用法點擊這裏
異步I/O
概述:利用這種技術,進程告訴內核,當描述符準備好進行I/O時,用一個信號通知它。
AIO控制塊
struct aiocb
{
int aio_fildes; /* file desriptor */
off_t aio_offset; /* file offset for I/O */
volatile void *aio_buf; /* buffers for I/O */
size_t aio_nbytes; /* numbers of bytes to transfer */
int aio_reqprio; /* priority */
struct sigevent aio_sigevent; /* signal information*/
int aio_lio_opcode; /* operation for list I/O*/
sigevent 事件
struct sigevent {
int sigev_notify; /* notify type*/
int sigev_signo; /* signal number */
union sigval sigev_value; /* notify argument */
void (*sigev_notrify_function)(union sigval); /* notify function */
pthread_attr_t *sigev_notify_attributes; /* notify attrs */
};
函數readv和writev
#include <sys/uio.h>
ssize_t readv(int filedes,const struct iovec *iov,int iovcnt);
ssize_t writev(int filedes,const struct iovec *iov,int iovcnt);
參數:filedes 文件描述符
iov 指向iovec結構數組的一個指針。
iovcnt 數組元素的個數
返回值:若成功則返回已讀、寫的字節數,若出錯則返回-1
struct iovec {
void *iov_base; /* 起始地址 */
size_t iov_len; /* 需要傳輸的字節數 */
};
readv() 系統調用從文件描述符 fd 關聯的文件裏讀取數據到 iovcnt 個由 iov 結果描述的緩存區裏。(分散讀)
writev() 系統調用把 iovcnt 個由 iov 結構描述的緩存區數據寫入文件描述符 fd 關聯的文件裏。(聚合寫)
存儲映射I/O
mmap函數
#include <sys/mman.h>
void *mmap (void *addr, size_t len, int prot, int flag, int fd, __off_t off);
addr:指定映射區的起始地址,0表示由系統選擇,該函數返回該映射區的起始地址
fd:被映射文件的描述符
len:映射的字節數
off:映射字節在文件中的起始偏移量
prot:映射存儲區的保護請求,對指定映射區的保護請求不能超過文件open模式訪問權限
prot | 說明 |
---|---|
PORT_READ | 可讀 |
PORT_WRITE | 可寫 |
PORT_EXEC | 可執行 |
PORT_NONE | 不可訪問 |
flag:影響映射存儲區的屬性
(1)MAP_SHARED:存儲操作會修改映射的文件
(2)MAP_PRIVATE:對映射區的存儲操作會創建該映射文件的一私有副本,不會影響原文件
mprotect函數
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
地址參數addr必須是系統頁長的整數倍
如果修改的頁時通過MAP_SHARED標誌映射到地址空間的,那麼修改不會立即寫會到文件中,何時寫會由內核的守護進程決定,如果只修改了一頁中的一個字節,整個頁都會被寫會
可以調用msync將該頁沖洗到被映射的文件中
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
進程終止時,會自動解除存儲映射區的映射,直接調用munman函數也可以解除映射區,
關閉映射存儲區時使用的文件描述符並不解除映射區
#include <sys/mman.h>
int mumap(void *addr, size_t len);
調用munmap並不會使映射區的內容寫會到磁盤上
多進程利用存儲映射I/O複製文件
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int N = 5; //默認爲5個
if (argc < 3 || argc > 4)
sys_err("please enter like this: ./a.out file_src file_dst [process_number]");
if (argc == 4)
N = atoi(argv[3]); //獲取輸入的進程個數
int fd_src = open(argv[1], O_RDONLY);
if (fd_src < 0)
sys_err("open source file error");
int fd_dst = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd_dst < 0)
sys_err("open dst file error");
struct stat sbuf;
int ret = fstat(fd_src, &sbuf);
if (ret < 0)
sys_err("read file information error");
int flen = sbuf.st_size;
if (flen < N)
N = flen;
ret = ftruncate(fd_dst, flen);
if (ret < 0)
sys_err("ftruncate error");
char *mp_src = (char *)mmap(NULL, flen, PROT_READ, MAP_SHARED, fd_src, 0);
if (mp_src == MAP_FAILED)
perror("mmap error");
close(fd_src);
char *mp_dst = (char *)mmap(NULL, flen, PROT_READ | PROT_WRITE, MAP_SHARED, fd_dst, 0);
if (mp_dst == MAP_FAILED)
perror("mmap error");
close(fd_dst);
int num = flen / N;
int remainder = flen % num; //均分後剩下的部分
pid_t pid;
int i;
for(i = 0; i < N; ++i) {
printf("create %dth proc\n",i);
pid = fork();
if (pid == 0)
break;
}
if (i == N) {
for(int j = 0; j < N; ++j)
wait(NULL);
}
else if (i == N-1) {
memcpy(mp_dst + i*num, mp_src + i*num, num + remainder);
}
else {
memcpy(mp_dst + i*num, mp_src + i*num, num);
}
munmap(mp_src, flen);
munmap(mp_dst, flen);
return 0;
}