关于io: 只要涉及到文件的IO操作,就必然有文件描述符这个东西,所以本章所有的IO高级操作,都是使用fd来实现的。

关于阻塞IO的demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}


int main(void)
{	
	int cor; //存放鼠标光标的坐标
	int mousefd = -1;
	int ret = 0;
	char buf[100] = {0};

	//mousefd = open("/dev/input/mouse0", O_RDONLY|O_NONBLOCK);
	//if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno);

	
	//重设0
	//fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);
	
	//补设
	int flag = fcntl(0, F_GETFL);
	flag = flag | O_NONBLOCK;
	fcntl(0, F_SETFL, flag); 
	

	while(1)
	{
		//printf("111\n");
		//ret = read(mousefd, &cor, sizeof(cor));	
		//if(ret > 0) printf("%u\n", cor);
		//else if(ret==-1 && errno!=EAGAIN) print_err("read mouse0 fail", __LINE__, errno);
			
		//ret = read(0, buf, sizeof(buf));	
		ret = scanf("%s", buf);
		if(ret > 0) printf("%s\n", buf);
		//printf("222\n");
	}
	
	return 0;
}

监控鼠标操作,阻塞demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}


int main(void)
{	
	int cor; //存放鼠标光标的坐标
	int mousefd = -1;
	int ret = 0;
	char buf[100] = {0};

	mousefd = open("/dev/input/mouse0", O_RDONLY);
	//mousefd = open("/dev/input/mouse0", O_RDONLY|O_NONBLOCK);
	if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno);

	
	//重设0
	//fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);
	
	//补设
//	int flag = fcntl(0, F_GETFL);
	//flag = flag | O_NONBLOCK;
	//fcntl(0, F_SETFL, flag); 
	

	while(1)
	{
		printf("111\n");
		ret = read(mousefd, &cor, sizeof(cor));	
		if(ret > 0) printf("%u\n", cor);
		else if(ret==-1 && errno!=EAGAIN) print_err("read mouse0 fail", __LINE__, errno);
			
		//ret = read(0, buf, sizeof(buf));	
		//ret = scanf("%s", buf);
		//if(ret > 0) printf("%s\n", buf);
		//printf("222\n");
	}
	return 0;
}

非阻塞

实现非阻塞的两种方式

  1. 打开文件时指定O_NONBLOCK状态标志
  2. 通过fcntl函数指定O_NONBLOCK来实现

例如

mousefd = open("/dev/input/mouse0", O_RDONLY|O_NONBLOCK);

或者通过fcntl重设

mousefd = open("/dev/input/mouse0", O_RDONLY);
fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);

或者通过fcntl补设

flag = fcntl(0, F_GETFL); 	//获取原有文件状态标志
flag = flag | O_NONBLOCK; 	//通过|操作,在已有的标志上增设O_NONBLOCK
fcntl(0, F_SETFL, flag); 		//将修改后的“文件状态标志”设置回去

文件锁

当多个进程共享读写同一个文件时,为了不让进程们各自读写数据时相互干扰,我们可以使用进程信号量来互斥实现,除了可以使用进程信号量以外,还可以使用“文件锁”来实现,而且功能更丰富,使用起来相对还更容易些。

我们在来看一下fcntl函数

#include <unistd.h>
#include <fcntl.h>			
int fcntl(int fd, int cmd, .../*struct flock *flockptr */ );
第三个参数是...,fcntl函数是一个变参函数,第三个参数用不到时就不写。

fcntl函数有多种功能,我们这里主要介绍实现文件锁的功能,当cmd被设置的是与文件锁相关的宏时,fcntl就是用于实现文件锁。

参数

1)fd:文件描述符,指向需要被加锁的文件。
2)cmd:实现文件锁时,cmd有三种设置,F_GETLK、F_SETLK和F_SETLKW含义如下:
					 
(a)F_GETLK
	从内核获取文件锁的信息,将其保存到第三个参数,此时第三个参数为struct flock *flockptr。我们这里是要设置文件锁,而不是获取已有文件锁的信息,我们这里用不到这个宏。
								
(b)F_SETLK
	设置第三个参数所代表的文件锁,而且设置的是非阻塞文件锁,也就是如果加锁失败不会阻塞。也就是说加锁失败后如果不想阻塞的话,就是由F_SETLK宏来决定的。
								
	此时需要用到第三个参数,struct flock *flockptr。
								
	使用举例:
	· 第一步:定义一个struct flock flockptr结构体变量(这个结构体变量就是文件锁)。
	· 第二步:设置flockptr的成员,表示你想设置什么样的文件锁。
	· 第三步:通过第三个参数,将设置好的flockptr的地址传递给fcntl,设置你要的文件锁
							
							
(c)F_SETLKW
	与F_SETLK一样,只不过设置的是阻塞文件锁,也就说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。

demo如下

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "file_lock.h"


void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}

int main(void)
{	
	int fd = 0;
	int ret = 0;
	
	fd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
	if(fd == -1) print_err("./file", __LINE__, errno);

	ret = fork();
	if(ret > 0)
	{
		
		while(1)
		{
			SET_WRFLCK_W(fd, SEEK_SET, 0, 0);
			write(fd, "hello ", 6);
			write(fd, "world\n", 6);
			UNLCK(fd, SEEK_SET, 0, 0);
		}
	}	
	else if(ret == 0)
	{	
		while(1)
		{
			SET_WRFLCK_W(fd, SEEK_SET, 0, 0);
			write(fd, "hello ", 6);
			write(fd, "world\n", 6);
			UNLCK(fd, SEEK_SET, 0, 0);
		}
	}	
	
	return 0;
}	

其中file_lock.h

#ifndef H_FILELOCK_H
#define H_FILELOCK_H

#include <unistd.h>
#include <fcntl.h>

//非阻塞设置写锁
#define SET_WRFLCK(fd, l_whence, l_offset, l_len)\
	set_filelock(fd, F_SETLK, F_WRLCK, l_whence, l_offset, l_len)
//阻塞设置写锁
#define SET_WRFLCK_W(fd, l_whence, l_offset, l_len)\
	set_filelock(fd, F_SETLKW, F_WRLCK, l_whence, l_offset, l_len)

//非阻塞设置读锁
#define SET_RDFLCK(fd, l_whence, l_offset, l_len)\
	set_filelock(fd, F_SETLK, F_RDLCK, l_whence, l_offset, l_len)
//阻塞设置读锁
#define SET_RDFLCK_W(fd, l_whence, l_offset, l_len)\
	set_filelock(fd, F_SETLKW, F_RDLCK, l_whence, l_offset, l_len)

//解锁
#define UNLCK(fd, l_whence, l_offset, l_len)\
	set_filelock(fd, F_SETLK, F_UNLCK, l_whence, l_offset, l_len)

/* 调用这个函数,即可实现阻塞加读锁/阻塞加写锁, 非阻塞加读锁/非阻塞加写锁/解锁 */
static void set_filelock(int fd, int ifwait, int l_type, int l_whence, int l_offset, int l_len)
{
	int ret = 0;	
	struct flock flck;
	
	flck.l_type = l_type;
	flck.l_whence = l_whence;
	flck.l_start = l_offset;
	flck.l_len = l_len;

	ret = fcntl(fd, ifwait, &flck);
	if(ret == -1)
	{
		perror("fcntl fail");
		exit(-1);
	}
} 



#endif

io多路复用

对于多路io来说,只有操作阻塞的fd才有意义,如果文件描述符不是阻塞的,使用多路IO没有意义。多路IO有两种实现方式,分别是poll和select,其中select会比poll更常用些。

其中select函数原型

#include <sys/select.h>
					
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 
struct timeval *timeout)

demo如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}

int main(void)
{
	int ret = 0;
	int mousefd = 0;
	char buf1[100] = {0};
	int buf2 = 0;
	fd_set readfds;
	struct timeval timeover;
	
	mousefd = open("/dev/input/mouse0", O_RDONLY);
	if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno);
	
		
	while(1)
	{
		/* 经0和mousefd加入读集合 */
		FD_ZERO(&readfds);
		FD_SET(0, &readfds);
		FD_SET(mousefd, &readfds);
		
		/* 设置超时时间 */
		timeover.tv_sec = 3;	
		timeover.tv_usec = 0;

		/* select监听:如果集合没有动静就阻塞 */
	lable:	ret = select(mousefd+1 , &readfds, NULL, NULL, &timeover);
		if(ret==-1 && errno==EINTR) goto lable;
		else if(ret == -1) print_err("select fail", __LINE__, errno);
		else if(ret > 0) 
		{
			if(FD_ISSET(0, &readfds))
			{
				bzero(buf1, sizeof(buf1));
				ret = read(0, buf1, sizeof(buf1));	
				if(ret > 0) printf("%s\n", buf1);
			}		
			if(FD_ISSET(mousefd, &readfds))
			{
				bzero(&buf2, sizeof(buf2));
				ret = read(mousefd, &buf2, sizeof(buf2));	
				if(ret > 0) printf("%d\n", buf2);
			}
		}
		else if(ret == 0)
		{
			printf("time out\n");
		}
	}	
	
	return 0;
}

io多路复用之poll函数原型

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

demo如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>



void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}


int main(void)
{
	int ret = 0;
	int mousefd = 0;
	char buf1[100] = {0};
	int buf2 = 0;
	struct pollfd fds[2];
	
	mousefd = open("/dev/input/mouse0", O_RDONLY);
	if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno);
		
	fds[0].fd = 0;
	fds[0].events = POLLIN;

	fds[1].fd = mousefd;
	fds[1].events = POLLIN; //期望的事件

	while(1)
	{
	lable:	ret = poll(fds, 2, 3000);	
		if(ret==-1 && errno==EINTR) goto lable; //重启系统调用
		else if(ret == -1) print_err("poll fail", __LINE__, errno);
		if(ret > 0) 
		{
			if(fds[0].events == fds[0].revents)
			{
				bzero(buf1, sizeof(buf1));
				ret = read(fds[0].fd, buf1, sizeof(buf1));
				if(ret > 0) printf("%s\n", buf1);
			}
			if(fds[1].events == fds[1].revents)
			{
				bzero(&buf2, sizeof(buf2));
				ret = read(fds[1].fd, &buf2, sizeof(buf2));
				if(ret > 0) printf("%d\n", buf2);
			}
		}	
		else if(ret == 0) printf("tome out\n");		
	}	
	
	return 0;
}

异步io

异步IO的原理就是,底层把数据准备好后,内核就会给进程发送一个“异步通知的信号”通知进程,表示数据准备好了,然后调用信号处理函数去读数据,在没有准备好时,进程忙自己的事情。

这就好比我跟澡堂老板说一声“有位置了打电话给我啊”,我就回去该忙啥就忙啥了,等老板通知我了我就知道有位置了,这样的方式不就更好吗。

比如使用异步IO读鼠标,底层鼠标驱动把数据准备好后,会发一个“SIGIO”(异步通知信号)给进程,进程调用捕获函数读鼠标,读鼠标的SIGIO捕获函数需要我们自己定义。

使用异步IO时,应用层的设置步骤

(1)调用signal函数对SIGIO信号设置捕获函数在捕获函数里面实现读操作,比如读鼠标。

(2)使用fcntl函数,将接收SIGIO信号的进程设置为当前进程如果不设置的,底层驱动并不知道将SIGIO信号发送给哪一个进程。

fcntl(mousefd, F_SETOWN, getpid());	

(3)使用fcntl函数,对文件描述符增设O_ASYNC的状态标志,让fd支持异步IO

mousefd = open("/dev/input/mouse1", O_RDONLY); 
flag = fcntl(mouse_fd, F_GETFL);
flag |= O_ASYNC;	//补设O_ASYNC
fcntl(mouse_fd, F_SETFL, flag);

异步io demo如下

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>



void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}


int mousefd = 0;
void signal_fun(int signo)
{
	int buf;
	int ret = 0;

	if(SIGIO == signo)
	{
		bzero(&buf, sizeof(buf));
		ret = read(mousefd, &buf, sizeof(buf));
		if(ret > 0) printf("%d\n", buf);
	}
}

int main(void)
{
	int ret = 0;
	char buf[100] = {0};
	struct pollfd fds[2];
	
	mousefd = open("/dev/input/mouse0", O_RDONLY);
	if(mousefd == -1) print_err("open mouse0 fail", __LINE__, errno);
		
	//为SIGIO设置捕获函数,在捕获函数里面读鼠标	
	signal(SIGIO, signal_fun);
	
	//告诉鼠标驱动,他发送的SIGIO信号由当前进程接收
	fcntl(mousefd, F_SETOWN, getpid());	

	//对mousefd进行设置,让其支持异步IO
	int flg = fcntl(mousefd, F_GETFL);
	flg |= O_ASYNC;
	fcntl(mousefd, F_SETFL, flg);
	
	
	while(1)
	{
		bzero(buf, sizeof(buf));
		ret = read(0, buf, sizeof(buf));
		if(ret > 0) printf("%s\n", buf);
	}

	return 0;
}

存储映射

使用文件IO的read/write来进行文件的普通读写时,函数经过层层的调用后,才能够最终操作到文件,面对大量数据时显得很吃力,既然直接read、write很费劲,那我干脆抛弃read、write的操作,mmap采用直接映射的方式实现,mmap映射时,比如映射普通文件,其实就会将普通文件的硬盘空间的物理地址映射到进程空间的。

通常情况下,进程空间的虚拟地址只映射自己底层物理空间的物理地址,但是使用mmap时,他会将文件的硬盘空间的地址也映射到虚拟地址空间,这么一来应用程序就可以直接通过映射的虚拟地址操作文件,根本就不需要read、write函数了,使用地址操作时省去了繁杂的中间调用过程,可以快速对文件进行大量数据的输入输出。

mmap函数原型

 #include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

demo如下

 #include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>



void print_err(char *str, int line, int err_no)
{
        printf("%d, %s: %s\n", line, str, strerror(err_no));
        exit(-1);
}

int main(void)
{
	int srcfd = -1;
	int dstfd = -1;
	void *srcaddr = NULL;
	void *dstaddr = NULL;

	
	/* 打开源文件 */
	srcfd = open("./file_lock.h", O_RDWR);
	if(srcfd == -1) print_err("open file_lock.h fial", __LINE__, errno); 	
	
	/* 打开目标文件 */
	dstfd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
	if(dstfd == -1) print_err("open file fial", __LINE__, errno); 	
	
	/* mmap映射源文件 */	
	struct stat src_stat = {0};
	fstat(srcfd, &src_stat);//获取文件属性(主要是想得到文件的size)
	srcaddr = mmap(NULL, src_stat.st_size, PROT_READ, MAP_SHARED, srcfd, 0);
	if(srcaddr == (void *)-1) print_err("mmap srcfile fail", __LINE__, errno);
	
	/* mmap映射目标文件 */	
	ftruncate(dstfd, src_stat.st_size);
	/*
	参数1:指定映射的起始虚拟地址,如果制定NULL表示由mmap指定
	参数2: 要映射的长度
	参数3:指定映射后的操作权限,PROT_WRITE/PROT_READ/PROT_EXEC/PROT_NONE
	参数4:是否立即更新到文件中,指定MAP_SHARED,表示理解更新
	参数5:你要映射的那个文件的fd
	参数6:指定一个偏移,表示你要从文件的什么位置开始映射 */
	dstaddr = mmap(NULL, src_stat.st_size, PROT_WRITE, MAP_SHARED, dstfd, 0);
	if(dstaddr == (void *)-1) print_err("mmap dstfile fail", __LINE__, errno);	
	
	
	/* 想办法讲源文件的数据复制到目标文件中 */
	memcpy(dstaddr, srcaddr, src_stat.st_size);

	
	return 0;	
}