file_operations 与 驱动实现要求


file_ops 中 最重要的概念莫过于 同步异步,阻塞非阻塞 概念及其实现.

  • 阻塞 非阻塞
同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)

所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由*调用者*主动等待这个*调用*的结果。

而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用。

---

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
其实如果是循环的只等待消息结果,就跟调度出去(挂起)没什么两样.也被称作阻塞.(私人理解)

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

  • 宏观上的概念

同步,
	只有ops->read直接监听的,都是同步 // 什么时候读确定
异步
	不只有 ops->read 直接监听的,或者根本没有 ops_>read 参与的,都是异步 // 什么时候读不确定
阻塞,
	进程会换出,进程会循环卡死到某处
非阻塞
	进程不会因为api 被换出

同步非阻塞
	read NON_BLOCK

同步阻塞
	read BLOCK

异步阻塞
	select,poll方案(阻塞) (异步通知方案)加 read NON_BLOCK ,并不是直接read监听的,都是异步非阻塞 // 也被称作多路复用
	
异步非阻塞
	epoll 方案
		epoll 方案是将poll 方案针对 socket 进行的优化,一般用于socket fd 的监听
	fasync (非阻塞) 加 read NON_BLOCK(在信号中) // 也被称作 信号驱动IO
	aio 方案 // 也被称作异步IO // aio方案有三种(https://my.oschina.net/yangpeng/blog/631905 http://blog.sina.com.cn/s/blog_533074eb01013zdp.html)当前介绍的是glibc 实现的aio方案
		aio_read aio_error (循环检测,阻塞,没有换出) , aio_return // aio_return 得到读出的结果 // 其实是 异步阻塞方案
		aio_read aio_suspend (阻塞,换出) , aio_return // aio_return 得到读出的结果 //异步非阻塞方案
		填充 aio_sigevent (注册信号),aio_read , 有信号来时,执行信号函数(在函数中,aio_error,aio_return ) // 异步非阻塞方案
		注意: aio_return 为 非阻塞函数.
	
  • 阻塞

阻塞
// 读
	fifo is empty
		if(file->f_flags & O_NONBLOCK)
			return _EAGAIN;
		wait_event_interruptible(read_queue)
// 其他路径
	wake_up(read_queue);
  • 非阻塞

非阻塞
// 读
	fifo is empty
		if(file->f_flags & O_NONBLOCK)
			return _EAGAIN;


  • 异步阻塞 poll

多路复用
	poll
	    unsigned int mask = 0;
	    poll_wait(file, &button_waitq, wait); /* 将进程挂接到button_waitq等待队列下 */ // 该api不会引起阻塞,  // poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的
	    /* 根据实际情况,标记事件类型 */
	    if (ev_press)
	        mask |= POLLIN | POLLRDNORM;
	
	    /* 如果mask为0,那么证明没有请求事件发生;如果非零说明有时间发生 */
	    return mask;
	//----用户空间
	poll
	//-----------内核空间
		sys_poll
			do_sys_poll
				do_poll
					for(;;)
						do_pollfd(pfd, pt)
							file->f_op->poll(file, pwait);
					schedule_timeout(__timeout);
	// --- 内核空间
	除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因

task 1
    DECLARE_WAITQUEUE(wait, current);
    add_wait_queue(w_wait, wait);
    __set_current_state(TASK_INTERRUPTIBLE);
    schedule();
    //task1 进程休眠
    ................//唤醒后从这儿执行
task 2 //唤醒task1
   wake_up(w_wait);

//可唤醒处于TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE状态的进程
#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)

//只能唤醒处于TASK_INTERRUPTIBLE状态的进程
#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

经过以上驱动程序的poll()函数应该返回设备资源的可获取状态,即POLLIN,POLLOUT,POLLPRI,POLLERR,POLLNVAL等宏的位"或"结果.每个宏的含义都表示设备的一种状态,如:

常量	说明
POLLIN	普通或优先级带数据可读
POLLRDNORM	普通数据可读
POLLRDBAND	优先级带数据可读
POLLPRI	高优先级数据可读
POLLOUT	普通数据可写
POLLWRNORM	普通数据可写
POLLWRBAND	优先级带数据可写
POLLERR	发生错误
POLLHUP	发生挂起
POLLNVAL	描述字不是一个打开的文件

SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, int, timeout_msecs)
	do_sys_poll(ufds, nfds, to);
		do_poll(nfds, head, &table, end_time);
			poll_table* pt = &wait->pt;
			for (;;) {
				for (; pfd != pfd_end; pfd++) {
					do_pollfd(pfd, pt, &can_busy_loop, busy_flag);
				}
				poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack);
			}
		__put_user(fds[j].revents, &ufds->revents)

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait,
	pwait->_key = pollfd->events|POLLERR|POLLHUP;
	mask = f.file->f_op->poll(f.file, pwait); // 由驱动中设置.
	mask &= pollfd->events | POLLERR | POLLHUP;
	pollfd->revents = mask; // 用户空间在poll 返回之后,会在用户空间检查这个值.

#define FD_ISSET(fd, fdsetp)    __FD_ISSET (fd, fdsetp)
#define __FD_ISSET(d, s) \                                                          
  ((__FDS_BITS (s)[__FD_ELT (d)] & __FD_MASK (d)) != 0)

如果当前不可读(先调用驱动.poll确定是否可读,然后继续do_poll),那么在sys_poll->do_poll中当前进程就会睡眠在等待队列上,这个等待队列是由驱动程序提供的(就是poll_wait中传入的那个)。当可读的时候,驱动程序可能有一部分代码运行了(比如驱动的中断服务

程序),那么在这部分代码中,就会唤醒等待队列上的进程,也就是之前睡眠的那个,当那个进程被唤醒后do_poll会再一次的调用驱动程序的poll函数,这个时候应用程序就知道是可读的了。
  • 异步非阻塞 fasync


异步IO fasync
handler
	
main
	act.sa_sigaction = handler;
	sigaction(SIGIO,&act,&oldact)
	open
	fcntl() // 设置异步IO所有权
	fcntk() // 设置SIGIO信号
	fcntl()//获取文件flags
	fcntl() // 设置FASYNC
	while(1)

---

fops
	.fasync	 = demo_fasyc;
demo_fasyc
	fasyc_helper(fd,file,on,xxx);

write
	kill_fasync(xxx,SIGIO,POLLIN);




  • 异步非阻塞 aio

aio 方案
	libc
		线程和阻塞IO模拟
		#include <aio.h>
	linux
		内核fops->aio_read 实现
		.aio_read = pipe_read
		#include <libaio.h>
	libeio
		类似libc方案
		#include "eio.h"

异步IO aio_read
https://blog.csdn.net/summer_zgh/article/details/82416427
http://blog.sina.com.cn/s/blog_6028e2630100y0d1.html

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

int main()
{
        io_context_t ctx;
        unsigned nr_events = 10;
        memset(&ctx, 0, sizeof(ctx));  // It's necessary,这里一定要的
        int errcode = io_setup(nr_events, &ctx);
        if (errcode == 0)
                printf("io_setup success\n");
        else
                printf("io_setup error: :%d:%s\n", errcode, strerror(-errcode));

        // 如果不指定O_DIRECT,则io_submit操作和普通的read/write操作没有什么区别了,将来的LINUX可能
        // 可以支持不指定O_DIRECT标志
        int fd = open("./direct.txt", O_CREAT|O_DIRECT|O_WRONLY, S_IRWXU|S_IRWXG|S_IROTH);
        printf("open: %s\n", strerror(errno));

        char* buf;
        errcode = posix_memalign((void**)&buf, sysconf(_SC_PAGESIZE), sysconf(_SC_PAGESIZE));
        printf("posix_memalign: %s\n", strerror(errcode));

        strcpy(buf, "hello xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

        struct iocb *iocbpp = (struct iocb *)malloc(sizeof(struct iocb));
        memset(iocbpp, 0, sizeof(struct iocb));

        iocbpp[0].data           = buf;
        iocbpp[0].aio_lio_opcode = IO_CMD_PWRITE;
        iocbpp[0].aio_reqprio    = 0;
        iocbpp[0].aio_fildes     = fd;

        iocbpp[0].u.c.buf    = buf;
        iocbpp[0].u.c.nbytes = page_size;//strlen(buf); // 这个值必须按512字节对齐
        iocbpp[0].u.c.offset = 0; // 这个值必须按512字节对齐

        // 提交异步操作,异步写磁盘
        int n = io_submit(ctx, 1, &iocbpp);
        printf("==io_submit==: %d:%s\n", n, strerror(-n));

        struct io_event events[10];
        struct timespec timeout = {1, 100};
        // 检查写磁盘情况,类似于epoll_wait或select
        n = io_getevents(ctx, 1, 10, events, &timeout);
        printf("io_getevents: %d:%s\n", n, strerror(-n));

        close(fd);
        io_destroy(ctx);
        return 0;
}

  • glibc aio

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<fcntl.h>
#include<aio.h>
 
#define BUFFER_SIZE 1025
 
int main(int argc,char **argv)
{
    //定义aio控制块结构体
    struct aiocb wr;
 
    int ret,fd;
 
    char str[20] = {"hello,world"};
 
    //置零wr结构体
    bzero(&wr,sizeof(wr));
 
    fd = open("test.txt",O_WRONLY | O_APPEND);
    if(fd < 0)
    {
        perror("test.txt");
    }
 
    //为aio.buf申请空间
    wr.aio_buf = (char *)malloc(BUFFER_SIZE);
    if(wr.aio_buf == NULL)
    {
        perror("buf");
    }
 
    wr.aio_buf = str;
 
    //填充aiocb结构
    wr.aio_fildes = fd;
    wr.aio_nbytes = 1024;
 
    //异步写操作
    ret = aio_write(&wr);
    if(ret < 0)
    {
        perror("aio_write");
    }
 
    //等待异步写完成
    while(aio_error(&wr) == EINPROGRESS)
    {
        printf("hello,world\n");
    }
 
    //获得异步写的返回值
    ret = aio_return(&wr);
    printf("\n\n\n返回值为:%d\n",ret);
 
    return 0;
}

  • libeio
https://blog.csdn.net/lanyan822/article/details/7644745

linux下主要有两套异步IO,一套是由glibc实现的(以下称之为glibc版本)、一套是由linux内核实现,并由libaio来封装调用接口(以下称之为linux版本)

libeio 是 后期 开发的 , 类似 glibc 版本,相比 glibc 的aio 来说bug 比较少,而且架构清晰

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
 
#include "eio.h"
 
int respipe [2];
 
/*
 * 功能:子线程通知主线程已有回执放入回执队列.
 */
void
want_poll (void)
{
  char dummy;
  printf ("want_poll ()\n");
  write (respipe [1], &dummy, 1);
}
 
/*
 * 功能:主线程回执处理完毕,调用此函数
 */
void
done_poll (void)
{
  char dummy;
  printf ("done_poll ()\n");
  read (respipe [0], &dummy, 1);
}
/*
 * 功能:等到管道可读,处理回执信息
 */
void
event_loop (void)
{
  // an event loop. yeah.
  struct pollfd pfd;
  pfd.fd     = respipe [0];
  pfd.events = POLLIN;
 
  printf ("\nentering event loop\n");
  while (eio_nreqs ())
    {
      poll (&pfd, 1, -1);
      printf ("eio_poll () = %d\n", eio_poll ());
    }
  printf ("leaving event loop\n");
}
 
/*
 * 功能:自定义函数,用户处理请求执行后的回执信息
 */
int
res_cb (eio_req *req)
{
  printf ("res_cb(%d|%s) = %d\n", req->type, req->data ? req->data : "?", EIO_RESULT (req));
 
  if (req->result < 0)
    abort ();
 
  return 0;
}
 
int
main (void)
{
  printf ("pipe ()\n");
  if (pipe (respipe))
      abort ();
  printf ("eio_init ()\n");
  if (eio_init (want_poll, done_poll)) //初始化libeio库
      abort ();
  eio_mkdir ("eio-test-dir", 0777, 0, res_cb, "mkdir");    
  event_loop ();
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值