linux_api之高级IO
本篇索引:
1、引言
2、非阻塞IO
3、记录锁(文件锁)
4、io多路复用(I/O multiplexing )
5、异步IO
6、存储映射IO
1、引言
我们第三篇学习了对IO的open、read、write等的操作,这一篇我们将会学习对IO的一些高级操作,实际上这一篇的内容是对第三篇内容的进一步升华,主要的内容如下:
·非阻塞IO:对文件实现非阻塞操作。
·记录锁:利用文件实现锁的机制。
·IO多路复用:实现单线同时操作多个阻塞IO,分select和poll两种的操作。
·存储映射IO:mmap
本篇不重理论,重点是列举各种例子代码,教会大家如何使用这些高级IO的设置和使用。
2、非阻塞IO
2.1、低速系统调用之阻塞
上一篇说过,所有系统调用被分为两类,一类是低速系统调用,另一类是其它系统调用。
1)、低速系统调用:可能会使进程永远阻塞的一类系统调用,系统调用导致进程阻塞的原因
有两种。
a)函数操作文件时,因文件类型而阻塞,阻塞与函数本身无关
·读某些文件:由于数据不存在会导致调用者永远阻塞
读管道文件:管道是进程间通信用的特殊文件,读管道时,如果管道中并无数据会导致
对管道的读操作会阻塞。
读终端设备:如读鼠标、键盘等字符设备类文件,以及网络设备文件时,如果没有数据
的话,读操作也会阻塞。
注意:值得强调的是,低速系统调用读磁盘I/O(普通文件)并非是阻塞的,如果有数
据会带着数据正常返回,如果没有数据则也会返回,所以不会阻塞。
·写某些文件:在写某些文件时,当文件不能立即接收写入的数据时,也可能会导致写
操作长期的阻塞下去。
·打开文件:在某些条件成立之前可能有些文件是无法打开的,这同样可能会导致打开
操作长期的阻塞下去。
情况1:如果想要成功打开某些终端设备,那么你就必须等到某些调制解调器应答后才
能打开,否者会一直阻塞下去。
情况2:如果某个管道的读端没打开时,而你又想以写方式打开该管道,那么这个以写
方式打开的操作会一直阻赛直到某个地方以读打开这个管道为止。
b)某些函数本身就是阻塞的
pause函数,wait函数,sleep函数,某些ioctl操作,以及某些进程间通信的函数(如当消息队列的消息接受函数设置了阻塞时),这些函数调用本身就是阻塞的。
2)、其它系统调用:略
2.2、如何设置和修改阻塞为非阻塞
前面说过,某些文件默认打开后默认对文件的操作就是阻塞的,但是利用对文件描述符设置,可将其操作设置为非阻塞的,主要的方法有如下两种。
1)、打开文件时指定非阻塞,例子如下:
以非阻塞的方式打开标准输入文件。
int main(void){
int fd = -;
fd = open("/dev/stdin", O_RDONLY|O_NONBLOCK);
if(fd < ){
perror("open stdin is fail");
exit(-);
}
return ;
}
/dev/stdin是标准输入文件,对应着键盘输入,一般情况下默认就是以阻塞方式打开的,但如果我们在打开时指定O_NONBLOCK参数的话,就指定为了非阻塞,当我们去read该文件时就不会再阻塞了。
2)、用fcntl函数进行设置
上例中我们重新打开了标准输入文件,新返回的描述符fd(3)和描述符0同时指向了标准输入文件,虽然fd被设置为了非阻塞,但是描述符0任然是阻塞的。
因为0和3这两个文件描述符是分别各自调用open函数打开/dev/stdin文件而返回得到的,这种情况下各个文件描述符指向的文件结构关系如下:
从上图我们很明显的看到,0,3这两个描述符各自有一个文件表,可以设置自己的文件状态标志,所以0是阻塞的而3却是非阻塞的就不难理解了。那么如何将已经打开了的文件描述符设置为非阻塞的呢?这就又要使用到fcntl函数了,比如我们可以将已经打开了的0设置为非阻塞,代码实现如下:
int main(void)
{
int fd = -, flag = -; /* F_GETFL:获取描述符原有状态给flag的命令,目的是为了保护原有的状态
* STDIN_FILENO:指向标准输入文件的文件描述符0 */
flag = fcntl(STDIN_FILENO, F_GETFL);
flag |= O_NONBLOCK;//将原有文件状态 | 非阻塞标志
//将修改后的包含了非阻塞标志的新状态重新设置回去
fcntl(STDIN_FILENO, F_SETFL, flag); return ;
}
2.3、非阻塞举例
1)、同时阻塞地读键盘和读鼠标
我们在一个单进程里面同时实现键盘的输入和鼠标的输入,但是对于这两个低速系统调用默认情况的读都是阻塞的,所以这两个的输入会相互阻塞,如下例:
int main(void)
{
char buf[] = {};
int fd = -, ret = -; /* 打开鼠标文件 */
fd = open("/dev/input/mouse1", O_RDONLY);
if(fd < )
{
perror("open /dev/input/mouse1 is fail");
exit(-);
} /* 由于读键盘和读鼠标默认都是阻塞的操作,所以它们会相互阻塞 */
while()
{
/* 先读键盘 */
bzero(buf, sizeof(buf));
ret = read(, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf)); /* 后读鼠标 */
bzero(buf, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
} return ;
}
该程序必须在超级用户下运行,因为鼠标文件只能在超级用户下才能被打开,运行程序时由于先读的是键盘,它阻塞了鼠标,所以我们先输入鼠标是没有用的,当从键盘桥如数据后,键盘数据打印出来,这时进程又阻塞在了读鼠标处,所以此时从键盘敲入数据是没有用的,这时必须移动鼠标输入数据,才能回到读键盘处(标准输入处),由于鼠标数据是整形的坐标值,所打印出来是乱码。
2)、非阻塞地实现读键盘和鼠标
从前面我们知道,键盘和鼠标的读导致了相互的阻塞,我们输入时并不通畅,现在我们将它们都改为非阻塞的,那么它们就不会相互阻塞,输入就会变得通畅,对上例修改后的代码如下:
int main(void)
{
char buf[] = {};
int fd = -, ret = -; /* 以非阻塞方式打开鼠标文件的操作,0指向的标准输入在
* 进程创建时就已经打开 */
fd = open("/dev/input/mouse1", O_RDONLY|O_NONBLOCK);
if(fd < )
{
perror("open /dev/input/mouse1 is fail");
exit(-);
}
/* 将标准输入0也改为非阻塞的 */
flag = fcntl(STDIN_FILENO, F_GETFL);
flag |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, flag); /* 由于读键盘和读鼠标默认都被改为了非阻塞的操作,所
* 以它们不再会相互阻塞 */
while()
{
/* 先读键盘 */
bzero(buf, sizeof(buf));
ret = read(, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf)); /* 后读鼠标 */
bzero(buf, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
} return ;
}
上例黑体部分代码就是添加或修改后的部分,精工这么设置后,鼠标和键盘不再相互阻塞,所以运行这个程序时不必再忌讳谁先输入的问题了,谁先输入都可以。
只是这种非阻塞会导致进程时刻都处在循环的,这种轮询的机制会非常的消耗cpu资源,为了解决同时输入键盘和鼠标的问题,这并不是一个好的解决方法,我们前面学过了多进程,所以我们可以利用两个进程来时实现同时读鼠标和键盘,假如A进程读鼠标,B进程读键盘,虽然它们都是阻塞的,但确是各自阻塞各自的,它们互不相干扰。
3)、利用两个进程实现同时读键盘和鼠标
我们开两个进程,一个进程读键盘,一个进程读鼠标,由于进程本身就是迸发同时向前运行的,所以这里再也不需要将键盘和鼠标设置为非阻塞。例子的例子如下:
int main(void)
{
char buf[] = {};
int fd = -, ret = -; /* 开来两个进程,父进程读鼠标,子进程读键盘 */
ret = fork();
if(ret == ){
while()
{
/* 读键盘 */
bzero(buf, sizeof(buf));
ret = read(, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
}
}
else if(ret > )
{
fd = open("/dev/input/mouse1", O_RDONLY|O_NONBLOCK);
if(fd < )
{
perror("open /dev/input/mouse1 is fail");
exit(-);
}
while()
{
/* 读鼠标 */
bzero(buf, sizeof(buf));
ret = read(fd, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
}
}
return ;
}
例子中并没有将键盘和鼠标都设置为非阻塞的,但同样能够实现同时键盘和鼠标,但当后面我们学到了select、poll多路io机制和异步通知后,还可以用这些方法来解决同时读鼠标和键盘的问题。
3、记录锁(文件锁)
3.1、为什么需要记录锁
当多个进程试图对同一个文件都进行读写操作时,如下图所示:
我们肯定希不同进程之间各自的读写操作望满足如下条件,以保护各个进程向文件所写之数据不被篡改。
1)、当某个进程正在写文件时,其它所有进程肯定不能写,否者会相互篡改。
2)、当某个进程正在写文件时,其它所有进程不能够读,因为别人在没有写完之前读出
的数据是不完整的。
3)、当某个进程正在读时,其它所有的进程都可以共享的读,因为读不会造成数据篡改。
4)、当某个进程正在读时,其它所有的进程都不可以写,否者会导致读出的数据不完整。
总结以上几点就是:
1)、写与写之间互斥
2)、读与写之间互斥
3)、读与读之间共享 //读不会可以共享
为了实现按照上述保护方式对文件进行读写,我们引入了记录锁,记录的设置也需要用到fcntl函数,这个函数我们在第3篇时就已经学习过了,当时说过该函数有很多的功能,今天我们就学习如何利用fcntl函数设置记录锁。
记录锁:利用文件描述fd实现对其指向的文件内容进行加锁,记录锁又称区域锁。
1)、对整个文件内容加锁
2)、对文件某部分内容加锁
记录锁主要使用目的:
1)、保护整个文件或文件某区域的内容。
2)、并不需要保护文件内容,只是借用文件记录锁,以实现加锁(例如保证原子操作)。
记录锁的种类:
1)、建议性记录锁,本篇重点
2)、强制性记录锁,不讨论
准确来讲,对整个文件加锁的记录锁应该被称为文件锁,只对文件部分内容加锁的才应被称为记录锁或区域锁,但通常情况下我们并不对这两个名称加以区别,都统称为记录锁或文件锁。
3.2、fcntl函数设置建议性记录锁
1)、函数原型和所需头文件
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *flockptr */ );
2)、函数功能:该函数有多种功能,在这里我们主要讨论如何利用该函数进行设置记录锁。
3)、函数参数
·int fd:文件描述符,指向需要加锁的文件。
·int cmd:设置记录锁时,cmd有三种设置,F_GETLK,F_SETLK,F_SETLKW。
·第三个参数:当设置记录锁时,为struct flock *flockptr,一个指向struct flock 结构体的指针,该结构体中设置好了我们需要设置的记录所的各个参数。
4)、函数返回值
设置记录锁成功返回0,失败则返回-1, 并且errno被设置。
5)、注意
1)、fcntl函数是一个变参函数,一般情况下只需设置前两个参数,但是设置记录锁时,
fcntl函数需要使用到第三个参数。
2)、F_GETLK、F_SETLK和F_SETLKW含义如下:
·F_GETLK:决定由flockptr所描述的锁是否被另外一把锁所排斥(阻塞)。如果存在
一把锁,它阻止创建由flockptr所描述的锁,则这把现存的锁的信息写到flockptr指
向的结构中。如果不存在这种情况,则除了将ltype设置为FUNLCK之外flockptr所指向
结构中的其他信息保持不变。
·F_SETLK:设置由flockptr所描述的锁。如果试图建立一把按上述兼容性规则并不允
许的锁,则fcntl立即出错返回,此时errno设置为EACCES或EAGAIN。
·F_SETLKW:这是FSETLK的阻塞版本,命令名中的W表示等待(wait)。如果由于存
在其它锁,那么按兼容性规则由flockptr所要求的锁不能被创建,那么调用进程睡眠。
如果捕捉到信号则睡眠中断(可以手动重启这个系统调用)。如此直到设置成功为止。
3)、struct flock结构体如下:
·结构体原型
struct flock
{
short l_type; // Type of lock: F_RDLCK,F_WRLCK, F_UNLCK
short l_whence; //How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END
off_t l_start; // Starting offset for lock
off_t l_len; //Number of bytes to lock
pid_t l_pid; //PID of process blocking our lock(F_GETLK only)
}
·结构体成员说明
short l_type:记录锁类型
a)、F_RDLCK:读锁(或称共享锁)
b)、F_WRLCK:写锁
c)、F_UNLCK:解锁
short l_whence:加锁位置粗定位,设置同lseek的whence
a)、SEEK_SET:文件开始处
b)、SEEK_CUR:文件当前位置处
c)、SEEK_END:文件末尾位置处
off_t l_start:精定位,相对l_whence的偏移,设置同lseek的offset
off_t l_len:文件中需被加锁区域的字节数,当设置为0时,表示加锁到文件末尾。
pid_t l_pid:加锁进程的PID,仅对F_GETLK有用)
4)、加锁区域的起点可以是文件的尾端或超过文件尾端的位置,可以是文件的起始位置, 但是绝不能在文件起始位置前设置。
5)、如若l_len为0,则表示锁的区域从其起点(起点由lstart和lwhence决定)开始直
至最大可能位置为止。也就是不管添写到该文件中多少数据,它都处于锁的范围。每当
项文件写入新的内容后,加锁区域会自动延伸到文件新的尾端。
6)、为了锁整个文件,通常将lstart设置为0,l_whence设置为SEEK_SET,l_len设置为0。
7)、可实现对文件组合加锁,比如对文件0-100字节加读锁,对101-250加写锁。
8)、可实现对文件部分区域解锁,例如文件0-500字节都被加了写锁,但解锁时可只解
300-500解锁,而0-299区域的锁将任然存在。
6)、测试用例
·设置记录锁
记录锁的设置比较繁琐,为了避免麻烦,我们只写一个相关函数,这个函数在自定义的头文件record_lock.h中实现,那么如何调用该函数实现非阻塞地加读锁、写锁,或阻塞的加读锁、写锁以及解锁则由不同的宏来实现,实现该函数和宏的头文件如下:
/* fcntl函数需要用到的头文件 */
#include <unistd.h>
#include <fcntl.h> #define read_lock(fd, l_whence, l_offset, l_len) \
lock_fun(fd, F_SETLK, F_RDLCK, l_whence, l_offset, l_len)
#define read_lockw(wfd, l_whence, l_offset, l_len)\
lock_fun(fd, F_SETLKW, F_RDLCK, l_whence, l_offset, l_len)
#define write_lock(fd, l_whence, l_offset, l_len)\
lock_fun(fd, F_SETLK, F_WRLCK, l_whence, l_offset, l_len)
#define write_lockw(fd, l_whence, l_offset, l_len)\
lock_fun(fd, F_SETLKW, F_WRLCK, l_whence, l_offset, l_len)
#define unlock(fd, l_whence, l_offset, l_len)\
lock_fun(fd, F_SETLK, F_UNLCK, l_whence, l_offset, l_len) int lock_fun(int fd, int cmd, int l_type, int l_whence, off_t l_offset, off_t l_len)
{
struct flock f_lock;
f_lock.l_type = l_type;
f_lock.l_whence = l_whence;
f_lock.l_start = l_offset;
f_lock.l_len = l_len; return(fcntl(fd, cmd, &f_lock));
}
·测试记录锁
测试记录锁的实现同上,只有一个函数,但多种不同测试都由不同的宏实现。
/* fcntl函数需要用到的头文件 */
#include <unistd.h>
#include <fcntl.h> #define testlock(fd, l_whence, l_offset, l_len) test_lock(fd, l_whence, l_offset, l_len) int test_lock(int fd, int l_whence, off_t l_offset, off_t l_len)
{
int ret = ;
struct flock flock = {}; flock.l_whence = l_whence;
flock.l_start = l_offset;
flock.l_len = l_len; ret = fcntl(fd, F_GETLK, &flock);
if(ret < )
{
perror("in test_lock fcntl is fail");
exit(-);
}
else if(F_RDLCK == flock.l_type) printf("%d seted read_lock\n", flock.l_pid);
else if(F_WRLCK == flock.l_type) printf("%d seted write_lock\n", flock.l_pid);
else if(F_UNLCK == flock.l_type) printf("unlock\n"); return ;
}
·使用记录所的例子
大家请看下面这个例子:
int main(void)
{
int ret = -, fd = -; fd = open("./file", O_CREAT|O_RDWR, );
if(fd < ){
perror("open ./file is fail");
exit(-);
}
/* 父子进程并发的向file文件里写hello worrd\n */
ret = fork();
if(ret == ){
while()
{
write(fd, "hello ", );
write(fd, "world\n", );
}
else if(ret > )
{
while()
{
write(fd, "hello ", );
write(fd, "world\n", );
}
} return ;
}
上例中父/子进程并发地向文件file写“hello world\n”,打开file,看到写入的结果如下:
hello world
hello world
。。。。。。
hello world
hello hello world
hello world
hello world
hello world
。。。。。。
我们发现结果中居然出现了hello hello world的情况,而导致这个情况的原因是因为
write(fd, "hello ", 6)和write(fd, "world\n", 6)的这两个操作并不是一个原子操作,假当父进程
刚写入“hello ”之后,父进程就被立即切换到子进程,子进程紧接着就也写“hello ”,就
会导致这样的结果,分析入下图所示:
这对write(fd, "hello ", 6)和write(fd, "world\n", 6)不是原子操作的问题,我们可以利用我们的记录所来改进,改进后的代码如下:
/* 其它头文件自己添加 */
#include "record_lock.h"
int main(void)
{
int ret = -, fd = -;
fd = open("./file", O_CREAT|O_RDWR, );
if(fd < ){
perror("open ./file is fail");
exit(-);
} /* 父/子进程并发的向file文件里写hello world\n */
ret = fork();
if(ret == ){//子进程
while(){
write_lockw(fd, SEEK_SET, , );//对整个文件加锁
write(fd, "hello ", );
write(fd, "world\n", );
unlock(fd, SEEK_SET, , );//解锁
}
}
else if(ret > ){父进程
while(){
write_lockw(fd, SEEK_SET, , );// 对整个文件加锁
write(fd, "hello ", );
write(fd, "world\n", );
unlock(fd, SEEK_SET, , );//解锁
}
} return ;
}
运行修改后的代码(留意黑体部分),然后vi file,发现再也没有“hello hello world”的现象了,这是因为加了记录锁后,write(fd, "hello ", 6)和write(fd, "world\n", 6)变成了原子操作,分析如下图:
从上图的中可以看出,write(fd, "hello ", 6)和write(fd, "world\n", 6)被强制成为了一个原子操作,进程B在进程A执行完write(fd, "hello ", 6)和write(fd, "world\n", 6)之前是不会加锁成功的,会一直阻塞下去直到进程A执行完成write(fd, "hello ", 6)和write(fd, "world\n", 6)并成功的解锁之后,进程B才能加锁成功。
3.3、记录锁讨论
3.3.1、记录锁的实现
观察上图得到:
1)、当同一进程中多个文件描述符指向同一文件时,只要其中的任何一个文件描述符被关闭,那么该进程加在文件上的记录锁将会被关闭,因为同一个文件不管被打开或复制多少次,但是它们共享却只有一个锁链表。
2)、进程终止时(不管是正常或异常终止),该进程所加的记录锁全部被释放。
3)当某个进程相想对某个文件加锁时,会首先检查锁链表。
a)如果发现已经被加了一个读锁,该进程可加读锁,但是不可以加写锁。
b)如果发现已经被加了一个写锁,该进程不能加写锁,也不能加读锁。
4)、fork产生的子进程不会继承父进程所加的锁,因为锁的作用是阻止多个进程同时写同一个文件(或同一文件区域) 。如果子进程继承父进程的锁,则父、子进程就可以同时写同一个文件。如果子进程想加锁,必须自己重新调用fcntl函数重新加锁。
5)、在执行exec后,新程序可以继承原执行程序的锁。
6)、同意进程加多个
3.4、强制性记录/文件锁(仅做了解)
3.4.1、为什么需要强制性锁
我们前面讲的锁都是建议性锁,对于建议性锁存在一个问题,那就是当多个进程对文件进行读写操作,这些进程设置的记录锁,在相互之间是起作用的,但如果这时有一个除了这几个进程(该进程没有对该文件加锁)外另一个进程也去写这个文件,那么其他进程锁加的锁对这一个进程是没有任何作用的,那么该文件内容就会被这个进程的写操作所篡改,很多情况下我们是不希望出现这种情况的,针对这种情况我们就需要设置强制性的记录锁。
3.4.2、强制性记录性的加锁和启动
1)、加锁方式:同建议性记录锁
2)、如何启动强制性记录锁?
对会被加锁的文件打开其设置-组-ID位,关闭其组-执行位,如此就对该文件启动了强制性锁机制。
3.4.3、强制性记录锁对其它进程的影响
如果一个进程试图读、写一个强制性锁起作用的文件,而欲读、写的部分又由其他进程加上了读、写锁,此时会发生什么呢?对这一问题的回答取决于三方面的因素:
a)操作类型(read或write)
b)其它进程保有的锁的类型(读锁或写锁)
c)以及对该文件操作的有关描述符是阻塞还是非阻塞的。
如果一个进程试图open文件,而其它进程又对文件加了强制性锁,此时又会发生什么呢?
1)、如果open的flag标识中设置了O_TRUNC,立即出错返回,errno被设置EAGAIN。
对O_TRUNC情况出错返回是有意义的,因为其他进程对该文件持有读、写锁,所以不能将 其截短为0。
2)、如果open的flag标识中设置了O_CREAT,立即出错返回,errno被设置EAGAIN。
对OCREAT情况在返回时也设置errno则无意义,因为该标志的意义是如果该文件不存在则
创建,由于其它进程对该文件持有记录锁,因而该文件肯定是存在的。
注意:并不是所有的linux操作系统都支持强制性记录锁。
4、io多路复用(I/O multiplexing )
前面我们为了实现同时读键盘和鼠标,采用非阻塞或多进程来实现,但是这两种方法都有一定的缺陷,非阻塞会需要轮训,很消耗cpu资源,如果采用多进程时,进程之间切换也是很耗费资源的,并且当程之间需要相互共享资源的话,这就需要加入进程间通信机制,这就会使得我们的程序变得更加的复杂。
对此我们引入一种新的解决办法,多路io复用,其分为如下两种:
·select机制;
·poll机制;
不管是那种机制,多路io复用的是原理是一致的,其基本思想是构造一个文件描述符的表,在这个表里面存放了会阻塞文件描述符,然后调用多路复用函数,该函数每隔一段时间会去检查一次,看表中是否有某个或几个文件描述符有动作,没有就休眠一段时间,再隔一段再去检查一次,如果其中的一个或多个文件名描述符有了动作,函数返回不在休眠,将分别对其有动作的文件描述符做相应操作。
实际上多路复用本身也存在轮询检查过程,但是绝大部分时间却是在休眠,所以就避免了一般的轮询机制,以及多进程实现所带来的相当的资源损耗。多路复用原理如下图所示:
注意:一般情况下集合中都设置阻塞的文件描述符,设置非阻塞的描述符是没有意义的。
4.1、select,pselect函数
1)、函数原型和所需头文件
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
2)、函数功能
·这两个函数都是为了实现多路复用,但是这两个函数都能够被信号中断,但是pselect函数能够通过sigmask参数屏蔽掉那些我们不希望中断pselect系统函数的信号。
3)、函数参数
a)select函数
·int nfds:readfds, writefds, exceptfds这三个集合中最大描述符+1(因为描述符
是从0算起的),用以说明需要关心描述符的范围,这个范围必须包含所有集合中的文
件描述符。
·fd_set *readfds:读集合,设置读会阻塞的描述符。
·fd_set *writefds: 写集合,设置写会阻塞的描述符。
·fd_set *exceptfds: 设置异常描述符的集合。
·struct timeval *timeout:成员结构如下:
struct timeval
{
long tv_sec; /* seconds(秒) */
long tv_usec; /* microseconds (微秒)*/
};
(1)该参数填NULL表示不设置超时,这种情况下如果没有描述符响应,同时也没有 信号中断的话则永远阻塞。
(2)如果需要设置超时,则需填写设置了时间结构体的地址,如果没有描述符响应
但设置的时间却到了,立即返回而不再阻塞。
b)、pselect函数
·前四个参数:同select函数
·struct timespec *timeout:成员结构体如下:
struct timespec
{
long tv_sec; /* seconds(秒) */
long tv_nsec; /* nanoseconds (纳秒)*/
};
(1)该参数填NULL表示不设置超时,这种情况下如果没有描述符响应,同时也没有 信号中断的话则永远阻塞。
(2)如果需要设置超时,则需填写设置了时间结构体的地址,如果没有描述符响应
但设置的时间却到了,立即返回而不再阻塞。
·sigset_t *sigmask:信号屏蔽集,用于设置我们希望屏蔽的信号的,防止这些信号
中断pselect的调用。
4)、函数返回值
·返回-1:说明函数调用失败,errno被设置。
·返回0:超时时间到并且没有一个描述符有响应,返回0说明没有一个描述符准备好。
·返回值>0:返回有响应的文件描述符的数量。
5)、注意
a)当内核检测到集合中有某个或几个文件描述符有响应时,这个集合将会被重新设置,
用于存放那些有动作的文件描述符,所以一旦函数调用完毕我们必须重新设置这些集合。
b)当pselect被执行时,信号屏蔽字会被sigmask替换,相应的被设置了屏蔽的信号会
被屏蔽,但是这个函数一旦执行完毕,信号屏蔽字会被还原。
c)select和pselect都是会被信号中断的低速系统调用,当然我们可以手动重启该调用。
d)如果我们不希望select或pselect函数被信号中断,那么设置的方法如下:
·忽略那些我们不希望的信号。
·利用sigfilleset、。。。sigdelset、sigpromask等函数,去修改信号屏蔽字以屏 蔽这些信号。
·专门使用pselect函数,因为该函数调用期间,函数中的sigmask参数(设置了我们
希望屏蔽的信号)会去替换信号屏蔽字,被我们屏蔽的信号一旦发生后,就会成为未
决信号而被设置到了未决信号集中,但是该函数一旦调用完毕,信号屏蔽字会被还原,
以前因被屏蔽而被设置在未决信号集中的未决信号就可能会被响应(响应方式:调用
捕获函数或终止进程)。
e)文件描述符集合中设置的都是会导致阻塞的描述符,设置非阻塞的文件描述符没有太
大意义,所以集合中不要设置普通文件的描述符,因为不会阻塞。比如读普通文件时, 不管有没有数据,read函数都将返回。
f)select可以用来模拟精度为us级别的定时器,而pselect则可以用来模拟精度为
ns级别的定时器。
6)、测试用例
a)、select函数
·多路复用读键盘和鼠标,打开鼠标需要超级用户权限
void err_fun(const char *file_name, int line, const char *fun_name, int err_no)
{
fprintf(stderr, "in %s, %d fun %s is fail: %s\n", file_name, \
line, fun_name, strerror(err_no));
exit(-);
} int main(void)
{
sigset_t set; fd_set rdfds;
int mouse_fd = -, ret = , i = ;
char buf[] = {};
struct timeval tim = {}; #if 0
/* 防止信号终端select函数 */
/* 方法一:忽略信号 */
signal(SIGINT, signal_fun); for(i=; i<; i++) signal(i, SIG_IGN);
/* 方法二:屏蔽信号 */
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
#endif
/* 打开鼠标文件,需要在root用户才能下打开 */
mouse_fd = open("/dev/input/mouse1", O_RDONLY);
if(mouse_fd < ) err_fun(__FILE__, __LINE__, "open", errno); while()
{
/* 设置读集合,设置操作必须放在循环内 */
FD_ZERO(&rdfds); //清空读集合
FD_SET(mouse_fd, &rdfds); //将mouse_fd设置到读集合中
FD_SET(STDIN_FILENO, &rdfds); //将标准输入也设置到读集合中 /* 设置超时时间,设置必须放在循环内 */
tim.tv_sec = ; //秒
tim.tv_usec = ; //微秒 /* -如果集合中没有描述符响应
* 1.如果第四个参数被设为NULL,select将一直阻塞知道集
* 合中描述符有响应为止
* 2.如果第四个参数设置了超时时间,时间到则函数超时返回
* -如果集合中有一个或多个描述符响应,则集合被内核重新设
* 置,用于存放有响应的描述符,设置的超时时间也被清空 */
lab0: ret = select(mouse_fd+, &rdfds, NULL, NULL, &tim);
if(ret< && EINTR==errno) //重启被信号中断了的select系统调用
{
printf(" interrupt select\n");
goto lab0;
}
else if(ret < ) err_fun(__FILE__, __LINE__, "select", errno);
else if(ret > ) //集合中有
{ //mosue_fd是不是有响应的描述符
if(FD_ISSET(mouse_fd, &rdfds))
{
ret = read(mouse_fd, buf, sizeof(buf));//读鼠标
if(ret > ) write(, buf, strlen(buf));
}
if(FD_ISSET(, &rdfds)) //0是不是有响应的描述符
{
ret = read(, buf, sizeof(buf));//读键盘
if(ret > ) write(, buf, strlen(buf));
}
}
else if( == ret) printf("time out\n"); //超时
} return ;
}
·用select模拟精度为微秒级的定时器
void select_timer(int secs, int usecs)
{
sigset_t set = {};
int ret = , i = ;
struct timeval tim = {}; #if 0
/* 防止信号干扰
* 方法一:忽略信号 */
for(i=; i<; i++) signal(i, SIG_IGN); /* 方法二:屏蔽信号 */
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
#endif tim.tv_sec = secs; //秒
tim.tv_usec = usecs; //微秒 /* 做定时器时,除了超时设置,其余全部为设置0 */
lab0: ret = select(, NULL, NULL, NULL, &tim);
if(ret< && EINTR==errno)//重启被中断的系统调用
{
printf(" interrupt select\n");
goto lab0;
}
else if(ret < ) //出错处理
{
perror("select is fail");
exit(-);
}
else if( == ret) printf("time out\n");//超时
} int main(void)
{
/* 第一个参数:秒
* 第二个参数:微秒 */
select_timer(, );//调用select模拟的定时器
printf("hello\n"); return ;
}
b)、pselect函数
·同样打开鼠标需要超级用户权限,pselect代码与select的基本一致,只是多了最后一
个参数,用于设置屏蔽字。
·用pselect模拟精度为纳秒级的定时器,代码与select的基本一致,只是多了最后一
个参数,用于设置屏蔽字。
4.2、poll机制
实际上poll机制,与select差不多,只是具体调用的实现不一样,与select 不同,poll不是为每个条件构造一个描述符集,而是构造一个 pollfd结构数组,每个数组元素指定一个描述符编号以及对其所关心的条件。
1)、函数原型和所需头文件
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout,
const sigset_t *sigmask);
2)、函数功能
·这两个函数都是为了实现多路复用,这两个函数都能够被信号中断,但是ppoll函数能够通过sigmask参数屏蔽掉那些我们不希望中断ppoll系统调用的信号。
3)、函数参数
a)、poll函数
·struct pollfd *fds:struct pollfd结构体数组,每个数组成员设置了我们需要多路IO操作
的每个描述符,该结构体成员结构如下:
struct pollfd {
int fd; /* file descriptor:文件描述符 */
short events; /* requested events:设置我们希望发生的事件 */
short revents; /* returned events :实际发生的事件*/
};
·nfds_t nfds:结构体数组struct pollfd *fds的成员数量。
·int timeout:超时时间,单位为毫秒,如填写为3000,表示3秒超时,如果不希望设置
超时,该参数填写负数(如-1)即可。
b)、ppoll函数
·前两个参数同poll函数。
·const struct timespec *timeout:同pselect的超时设置的结构体。
a)、如果填写NULL, 在没有描述符响应时和信号中断时则永远阻塞。
b)、如果填写了设置超时的结构体地址,在没有描述符响应时则在超时后立即返回。
·sigset_t *sigmask:设置我们希望屏蔽的信号的,防止这些信号中断pselect的调用。
4)、函数返回值
·返回-1:说明函数调失败,errno被设置。
·返回0:超时时间到并且没有文件描述符有响应。
·返回值>0:返回有响应的文件描述符的数量。
5)、注意
a)当描述符有响应时,revents会被内核填写响应的类型,如果events==revents,说明这
个响应是我们希望的响应,利用该文件描述符实现相应操作,否则就不是我们希望的响
应,不做任何操作。
b)当ppoll被执行时,信号屏蔽字会被sigmask替换,相应的信号会被屏蔽,但是这个
函数一旦执行完毕,信号屏蔽字又会被还原为原来的信号屏蔽字,这一点与我们前面学
过的pselect是相同的。
c)poll和ppoll都是会被信号要中断的低速系统调用,但我们手动重启。
d)如果我们不希望poll、ppoll函数被信号中断,方法同select和pselect。
e)同select、pselect一样,对普通文件进行多路复用是没有意义的。
f)poll可模拟精度为ms级别的定时器,而ppoll则也可用来模拟精度为ns级定时的器。
e)poll的events和revents标志的设置如下表:
6)、测试用例
a)poll函数
·多路复用读键盘和鼠标,同样打开鼠标需要超级用户权限
void err_fun(const char *file_name, int line, const char *fun_name, int err_no)
{
fprintf(stderr, "in %s, %d fun %s is fail: %s\n", file_name, line, fun_name, strerror(err_no));
exit(-);
}
int main(void)
{
char buf[] = {};
struct pollfd fds[] = {};
int ret = -, mouse_fd = -; mouse_fd = open("/dev/input/mouse1", O_RDONLY); //打开鼠标
if(mouse_fd < ) err_fun(__FILE__, __LINE__, "mouse_fd", errno); /* 向数组中设置设置需要多路监听的描
* 述副,设置只需要设置一次就行 */
fds[].fd = ; //标准IO
fds[].events = POLLIN; //设置希望的事件,POLLIN:输入事件
fds[].fd = mouse_fd; //鼠标
fds[].events = POLLIN; //设置希望的事件,POLLIN:输入事件 #if 0
/* 防止信号终端select函数 */
/* 方法一:忽略信号 */
signal(SIGINT, signal_fun);
for(i=; i<; i++) signal(i, SIG_IGN);
/* 方法二:屏蔽信号 */
sigfillset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
#endif
while()
{
/* fds:struct pollfd结构提数组,2:需要多路监听的数量,3000:超时时间 */
lab0: ret = poll(fds, , );
if(ret< && EINTR==errno) //重启被信号中断了的poll调用
{
printf("interrupt poll\n");
goto lab0;
}
else if(ret < ) err_fun(__FILE__, __LINE__, "mouse_fd", errno);
else if( == ret) printf("time out\n"); //超时时间到
else if(ret > )
{
/* 如果响应事件revents等于希望的时间events,说明
* 该描述符由动作了,可以执行相应操作了 */
if(fds[].events == fds[].revents) //判断键盘是否有输入要求
{
bzero(buf, sizeof(buf));
ret = read(fds[].fd, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
}
if(fds[].events == fds[].revents)//判断鼠标是否由输入要求
{
bzero(buf, sizeof(buf));
ret = read(fds[].fd, buf, sizeof(buf));
if(ret > ) write(, buf, strlen(buf));
}
}
} return ;
}
·利用poll模拟ms定时器:略,自己仿照select自己实现。
a)、ppoll函数
·多路复用读键盘和鼠标,打开鼠标需要超级用户权限:略,仿照pselect自己实现。
·利用ppoll模拟ns定时器:略,仿照pselect自己实现。
5、异步IO
所谓异步io就是,当某个事件准备好,进程会被发送一个SIGIO的异步信号,进程受到这个信号的通知后,会调用信号处理函数去处理事件,在事件没有准备好时,进程并不需要轮询事件或者阻塞等待事件,进程可以忙自己的事情直到等到别人发送异步信号SIGIO通知某事件发生。
所谓异步就是,进程接收异步信号的时机完全是随机的,这个时机完全取决于事件发生的时刻,接受信号的进程是没有办法预测的。
异步IO设置的步骤如下:
(1) 调用signal或sigaction为该信号建立一个信号处理程序。
(2) 以命令F_SETOWN调用fcntl来设置接收信号进程PID和进程组GID。
(3) 以命令F_SETFL调用fcntl设置O_ASYNC状态标志,使在该描述符上可以进行异步I/O。第3步
仅用于指向终端或网络的描述符
5.1异步IO使用例子
·异步IO实现同时读键盘和鼠标
void signal_fun(int signo)
{
char buf[] = {};
int ret = -; memset(buf, , sizeof(buf));
ret = read(mouse_fd, buf, sizeof(buf));//读鼠标
if(ret > ) write(, buf, strlen(buf));
else if(ret < )
{
perror("read is fail");
exit(-);
}
} int main(void)
{
char buf[] = {};
int flag = -, ret = -; mouse_fd = open("/dev/input/mouse1", O_RDONLY); //打开鼠标字符设备文件
if(mouse_fd < )
{
perror("open mouse1 is fail");
exit(-);
}
/* 对mouse_fd设置异步IO */
flag = fcntl(mouse_fd, F_GETFL);
flag |= O_ASYNC;
fcntl(mouse_fd, F_SETFL, flag);
fcntl(mouse_fd, F_SETOWN, getpid()); //设置当前进程获取SIGIO信号
signal(SIGIO, signal_fun);//捕获SIGIO信号 while()
{
memset(buf, , sizeof(buf));
ret = read(, buf, sizeof(buf));//读键盘
if(ret > ) write(, buf, strlen(buf));
else if(ret < )
{
perror("read is fail");
exit(-);
}
}
return ;
}
6、存储映射IO
6.1、存储映射的好处
我们以前为了实现对文件的数据输入/输出,我们用的都是read,write等函数,这些函数处理数据时,数据需要在和各级缓冲区之间进行数据的复制,当要面对大量数据读写时,这些函数调用很费时间,如果我们能够直接通过地址对文件进行数据输入输出的话,将避免这样的缺点,本节的存储映射I/O就能实现这样的功能。
存储映射I/O使一个磁盘文件与虚拟存储空间中的一段缓存相映射。于是当从缓存中取数据,就相当于读文件中的相应字节。与其类似,将数据存入缓存,则相应字节就自动地写入文件。这样,就可以在不使用read和write的情况下执行I/O。
6.2、mmap函数
1)、函数原型和所需头文件
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
2)、函数功能
·mmap函数:将文件所在的磁盘空间映射到内存空间。
·munmap函数:取消映射。
3)、函数参数
a)、void *addr:在内存中映射时的映射起始地址。
·如果设置为NULL,由内核决定起始地址,这时最常见的方式。
·如果设置不为NULL,内核将采用该地址作为映射起始地址,如果这个地址不是内 存页的整数倍,内核会自动调整到最近的虚拟页整数倍的地址处。
b)、size_t length:映射的长度。
c)、int prot:指定对映射区的操作权限,可指定如下命令宏:
·PROT_EXEC: 映射区的内容可执行。
·PROT_READ: 映射区的内容可读。
·PROT_WRITE: 映射区的内容可写。
·PROT_NONE: 映射区不允许访问。
前三个选项可相互|操作,如果已经设置了前三个中一个或几个参数的话,设置第四个 参数就没有意义了,如果你想设置PROT_NONE,就不要设置前三个PROT_XXX参数。
d)、int flags:该标志决定对映射区的操作是否同步地更新文件内容,该映射区对于其它
也映射了文件该区域的进程是否可见,这些行为都有下述标志决定:
·MAP_SHARED:共享映射区。指定该标志后本进程对于映射区的修改将会被更新到 文件中,所以对于其它也映射了该区的进程来说文件的修改是可见的。
·MAP_PRIVATE:创建私有的写时复制映射区。指定了此标志后,对于映射区的修改
是不会去更新文件的,所以对于其它也映射了该文件的其它进程来说是不可见的。
• MAP_FIXED:返回值必须等于addr。由于该设置不利于可移植性,所以不鼓励使用此
标志,如果未指定此标志,但addr却非0,那么内核只把addr视为将何处设置为映射起
始地址的一种建议。
这里请注意,MAP_SHARED和MAP_PRIVATE不能同时指定。当然除了上面三个标志外,还有另外一些MAP_xxx标志值,详见情况请参见mmap(2)手册页,
e)、int fd:指向需要被映射文件的描述符。
f)、off_t offset:指定从文件起始位置偏移多少字节处开始映射。
4)、函数返回值
·mmap函数:函数调用成功,返回分配的映射地址,失败则返回(void*)-1,errno被设置。
·munmap函数:调用成功返回 0, 失败则-1, errno被设置。
5)、注意
a)设置addr和offset的值时,其值通常应该是虚拟页的整数倍,虚存页长度可用带参数
SC_PAGESIZE的sysconf函数得到。因为offset和addr常常指定为0,所以这种要求一般并 不是问题。
b)映射时遵守如下规则:
·需要被映射文件的长度不能为0,如果文件长度为0,则可以向文件写入一点数据或
者调用truncate函数对文件设置一个初始长度。
·映射时,不管length指定的大小是多少(length不能==0),真实映射的空间大小如下:
if(文件长度 % 虚拟页 != 0) 真实映射空间 = 虚拟页*(⌊文件大小/虚拟页 ⌋+1)
else if(文件长度 % 虚拟页 == 0) 真实映射空间 = 文件大小
·根据mmap时指定的映射长度length和真实映射空间的大小关系,映射空间的情况分
为如下几种情况:
情况一:length > 真实映射空间,映射情况如下:
-写有效映射空间:如果我们mmap时指定了PROT_SHARED标志的话,写入内存中内容
会同步更新到文件中。
-写虚空间:写操作是有效的,但是只是写到了内存中,文件并不会被更新,因为文 件长度不包含这部分。
-写无效映射空间:会导致SIGBUS信号的产生,对这个信号默认处理方式会终止进 程。实际上导致无效映射空间产生的有两种。
a)mmap时指定的length > 真实映射空间。
b)truncate将文件截断,真实映射的自动空间缩短,导致指定的length > 缩短
后的真实映射空间。
-当文件长度为0时,我们映射的空间都是无效映射空间。
情况二:length < 真实映射空间(length<文件长度就更不用说了),映射情况如下:
-写有效映射空间:如果我们mmap时指定了PROT_SHARED标志的话,写入内存中的内 容会被更新到文件中。
-写虚空间:写操作是有效的,但是只是写到了内存中,文件不会被更新的,因为文
件长度不包含这部分。
c)与存储映射有关的有两个信号,SIGBUS和SIGSEGV。
第一个信号产生的原因我们已经清楚,但是产生SIGSEGV信号的原因有哪些呢?
(1)、访问的空间并不存在
(2)、访问文件的权限不满足open时或者mmap是指定的权限。
d)open是指定的权限与mmap时指定的权限之间的关系
·open文件时权限一定要包含读权限,否者mmap会因权限不足而调用失败,比如如
果open文件时只指定O_WRONLY权限,mmap因权限受限而错误返回。
·mmap时指定的权限必须是open文件时所允许的权限,如果open文件时没有指定
写权限,即使mmap时指定了写权限,mmap时会因权限受限而错误返回。
但是如果open时指定了读权限,但是mmap时却没有指定读权限,对于这种情况确是允许读映射空间的,换句话说相当于mmap时会默认指定读权限。
e)在fork之后,子进程继承存储映射区(因为子进程复制父进程地址空间,而存储映射
区是该地址空间中的一部分),但是由于同样的理由,exec后的新程序则不继承此存 储映射区。
f)进程终止时,或调用了munmap之后,存储映射区就被自动去除。仅仅关闭文件描述符
filedes并不解除映射。
g)调用munmap并不会使映射区的内容更新磁盘文件上。因为对于映射后的磁盘文件的更
新,在写到存储映射区时会按内核虚存算法自动进行。
h)我们学到后面的LCD帧缓冲字符设备驱动时,我们还会接触到mmap函数,但是那个时
候不是为了映射磁盘文件,而是为了将虚拟内存中应用空间中的缓存和内核空间中的显
存映射起来,映射方和被映射都是内存。
我们知道显存是用来预存要被显示的图片数据的,但是我们如果应用程序直接使用write函数把图片数据写入到显存的话是非常耗费时间的,因为图片数据往往非常大,但是我们使用了的映射机制后,我们可以直接通过内存拷贝将应用空间存放的图片数据直
接复制到显存中,显然这种数据搬移方式对于大数据,其效率是非常高的。
6)、mmap的测试用例
·将内存中存放的多个学生信息写到文件中
struct student
{
int num;
char name[];
}; struct student stu[] = //定义一个学生结构提数组,初始化5个学生信息
{
{, "aaa"},
{, "bbb"},
{, "ccc"},
{, "ddd"},
{, "eee"},
}; int main(void)
{
void *addr = NULL;
int fd=-, n=, i=;
struct stat stat = {};
char buf[] = {}, temp_buf[] = {}; n = sizeof(stu)/sizeof(struct student);//计算学生人数 /* 以读写权限打开file文件,权限中必须包含读权限 */
fd = open("./file", O_RDWR|O_CREAT|O_TRUNC, );
if(fd < )
{
perror("open file is fail");
exit(-);
} ftruncate(fd, );//利用truncate函数将文件大小截为4000
fstat(fd, &stat); //获取文件属性,以便从中获取文件大小的属性
/* mmap,映射文件到内存空间中 */
addr = mmap(NULL, stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, );
if((void *)- == addr)
{
perror("mmap is fail");
exit(-);
} /* 将学生信息全部格式化转换字符串,以便写入文件后我们能够看懂这些学生信息 */
for(i=; i<n; i++) sprintf(buf+strlen(buf), "%d %s\n", stu[i].num, stu[i].name);
memcpy(addr, buf, strlen(buf)); //利用内存拷贝函数将内存中的5个学生信息到文件中 //同样利用内存拷贝函数将文件中学生信息读到内
//存应用应用空间中的temp_buf临时缓冲区中
memcpy(temp_buf, addr, n*sizeof(struct student));
printf("%s\n", temp_buf); //打印出临时缓冲区temp_buf中存放的学生信息 return ;
}
·将A文件中的内容赋值到B文件中
mmap.c
int main(void)
{
int fd1= -, fd2 = -;
struct stat stat = {};
void *addr1 = NULL, *addr2 = NULL; /* 以读写权限打开mmap.c文件,权限中必须包含读权限 */
fd1 = open("./mmap.c", O_RDWR|O_CREAT, );
if(fd1 < )
{
perror("open file is fail");
exit(-);
}
/* 以读写权限打开文件new_file.c,权限中必须包含读权限 */
fd2 = open("./new_mmap.c", O_RDWR|O_CREAT|O_TRUNC, );
if(fd2 < )
{
perror("open file is fail");
exit(-);
} //获取mmap.c的文件属性,以便从中获取文件大小的属性
fstat(fd1, &stat);
//利用truncate函数将new_mmap.c文件大小截为mmap.c的文件>大小
ftruncate(fd2, stat.st_size); /* mmap,映射mmap.c文件映射到内存空间中 */
addr1 = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd1, );
if((void *)- == addr1)
{
perror("mmap mmap.c is fail");
exit(-);
}
/* mmap,映射new_mmap.c文件映射到内存空间中 */
addr2 = mmap(NULL, stat.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd2, );
if((void *)- == addr2)
{
perror("mmap new_mmap.c is fail");
exit(-);
} //利用内存拷贝函数将mmap.c文件内存般移到new_mmap.c中
memcpy(addr2, addr1, stat.st_size); return ;
}
我们打开new_mmap.c会看到,mmap.c中的内容被拷贝到了new_mmap.c中。
linux_api之高级IO的更多相关文章
- (十一) 一起学 Unix 环境高级编程 (APUE) 之 高级 IO
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- 高级IO
# 高级IO 特殊的IO操作,包括文件锁.系统V的流.信号驱动的I/O.多路转I/O(select和pull函数).readv和writev函数以及存贮映射I/O等概念和函数. ## 文件锁 文件锁是 ...
- Java之高级IO,Properties
IO流(高级) 释放资源的标准代码 主要考虑的是在什么时候释放资源比较合适.而且在jdk1.7之前和之后是不同的. package com.wzlove.demo; import java.io.Fi ...
- 高级IO模型之kqueue和epoll
目录 简介 block IO和nonblocking IO IO多路复用和select poll epoll kqueue epoll和kqueue的优势 简介 任何一个程序都离不开IO,有些是很明显 ...
- 高级IO复用应用:聊天室程序
简单的聊天室程序:客户端从标准输入输入数据后发送给服务端,服务端将用户发送来的数据转发给其它用户.这里采用IO复用poll技术.客户端采用了splice零拷贝.服务端采用了空间换时间(分配超大的用户数 ...
- (51)LINUX应用编程和网络编程之六Linux高级IO
3.6.1.非阻塞IO 3.6.1.1.阻塞与非阻塞 阻塞:阻塞具有很多优势(是linux系统的默认设置),单路IO的时候使用阻塞式IO没有降低CPU的性能 补充:阻塞/非阻塞, 它们是程序在等待消息 ...
- 第14章——高级IO函数
1.套接字超时 套接字IO函数设置超时的方法有三种: (1)调用alarm. (2)select (3)使用SO_RECTIMEO和 SO_SNDTIMEO 选项 上面三种方法适用于输入输出操作(re ...
- Arduino101学习笔记(六)—— 高级IO
1.位移输出函数(8位) 输入value数据后Arduino会自动把数据移动分配到8个并行输出端. 其中dataPin为连接DS的引脚号, clockPin为连接SH_CP的引脚号, bitOrder ...
- 6 高级IO函数
6.1 pipe函数 pipe函数创建一个管道,用于实现进程间通信 #include<unistd.h> ]); 参数包含两个文件描述符fd[0]和fd[1],往fd[1]写入的数据可以从 ...
随机推荐
- linux系统上安装mysql5.6(详细步骤)
为了学习mycat 尝试在虚拟机上装mysql(看了别人的博客比划着安装),但装了两次都没成功.因此总结了如下步骤 有需要的朋友可以试下(linux需要联网) mysql-5.6.26.tar.gz百 ...
- HDP 中 yarn 和 MR2 的配置
以下说明均以集群中 slave 结点的配置为 48G内存,12块硬盘,12核(core) CPU 为例. 在 Yarn 中,一个 Container 是一个基础的包含内存和CPU 的单元.为了较好的平 ...
- react.js学习之路五
最近没时间写博客,但是我一直在学习react,我发现react是一个巨大的坑,而且永远填不完的坑 关于字符串的拼接: 在react中,字符串的拼接不允许出现双引号“” ,只能使用单引号' ',例如这样 ...
- css 引入的方式有哪些, link和@import的区别是什么
有四种形式: 1.链入外部样式表,就是把样式表保存为一个样式表文件,然后在页面中用<link rel = "stylesheet" type="text/css&q ...
- 在libuv中使用openssl建立ssl连接
在libuv中使用openssl建立ssl连接 @(blogs) 使用openssl进行加密通信时,通常是先建立socket连接,然后使用SSL_XXX系列函数在普通socket之上建立安全连接,然后 ...
- docker kubernetes swarm spring cloud结合学习资源
http://www.docin.com/p-2062732301.html https://blog.csdn.net/michael_hm/article/details/79213839 htt ...
- Python之路番外:PYTHON基本数据类型和小知识点
Python之路番外:PYTHON基本数据类型和小知识点 一.基础小知识点 1.如果一行代码过长,可以用续行符 \换行书写 例子 if (signal == "red") and ...
- selenium滑动验证码操作
1.首先要找到你要滑动的地方 2.调动鼠标事件按住不动 3.调整坐标即可 我这里是为了调试加了很多的sleep,print(hander)是为了看是否定位到了元素 4.效果如下图,但是我这里的验证文字 ...
- 网页footer背景(stick footer布局)
今天遇到了一个有意思的问题,想在网站的foot里面加入一张背景图片,并且在footer的底部写下一些内容于是乎在footer添加了background,并设置了footer的大小 先说一下开始的做法: ...
- jquery循环语句if-else if-else
jquery循环语句if-else if-elsecallbackFun()函数,开始是写的if-if-else.结果是不管第一个if有没有匹配到,会再次判断, 如果匹配到第二个if,则第二个if中的 ...