3、6高级IO

3.6.1.非阻塞IO   (文件锁的概念)

3.6.1.1、阻塞与非阻塞
阻塞:顾名思义,就是指在执行设备操作时若不能获得资源则 挂起 操作,直到满足可操作的条件后再进行操作,被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件满足。 函数不能立即返回

非阻塞:就是反过来,进程在不能进行设备操作时并不挂起,它或者放弃,或者不停的查询,直到可以进行位置。(函数立即返回,缺点是你不知道它是否执行成功了。)

3.6.1.2、为什么有阻塞式
(1)常见的阻塞:wait、pause、sleep等函数;read或write某些文件时 (这两个本身是非阻塞的,看你需要操作的文件是否是阻塞和非阻塞。)

(2) 非阻塞式 的好处 :利于发挥CPU的性能。当前进程等待,交出CPU的使用权,给其它进程使用。

3.6.1.3、非阻塞
(1)为什么要实现非阻塞: 有时候需要做些别的事情,没必要一直在那等待。
(2)如何实现非阻塞IO访问:O_NONBLOCK和fcntl

3.6.2.阻塞式IO的困境

3.6.2.1、程序中读取键盘、 ,键盘就是标准输入,stdin,对应文件描述符0
3.6.2.2、程序中读取鼠标、。鼠标需要open打开/dev/input/mouse1(我的鼠标设备文件是mouse0,具体哪一个文件具体分析)
3.6.2.3、程序中同时读取键盘和鼠标()
3.6.2.4、 问题分析 :先阻塞在了鼠标那里,等鼠标结束,再执行键盘,顺序反了没用。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char** argv)
{
    /*
    //    读取键盘 ,键盘就是标准输入,stdin,对应文件描述符0
    
    char buf[100];
    
    memset(buf, 0, sizeof(buf));
    printf("before read.\n");              //命令行的输出是行缓冲的,敲回车才能显示,没敲回车,是阻塞式的。
    read(0, buf, 5);
    printf("读出的内容是:[%s].\n", buf);
    */
/*    
    // 读取鼠标
    int fd = -1;
    char buf[200];
    
    fd = open("/dev/input/mouse0", O_RDONLY);   //我这个鼠标输出是mouse0
    if (fd < 0)
    {
        perror("open:");
        return -1;
    }
    
    memset(buf, 0, sizeof(buf));
    printf("before read.\n");
    read(fd, buf, 50);
    printf("读出的内容是:[%s].\n", buf);
    */
    
    //同时读取鼠标和键盘。 面临阻塞式问题,必须先用鼠标,再用键盘,否则面临阻塞。
    //先读鼠标,主要也是这些文件是属于阻塞式的。
    int fd = -1;
    char buf[200];
    
    fd = open("/dev/input/mouse1", O_RDONLY);   //我这个鼠标输出是mouse0、具体环境具体分析。尝试。
    if (fd < 0)
    {
        perror("open:");
        return -1;
    }
    
    memset(buf, 0, sizeof(buf));
    printf("before read.\n");
    read(fd, buf, 50);
    printf("读出的内容是:[%s].\n", buf);
    
    
    //再读键盘。
    memset(buf, 0, sizeof(buf));
    printf("before read.\n");              //命令行的输出是行缓冲的,敲回车才能显示,没敲回车,是阻塞式的。
    read(0, buf, 5);
    printf("读出的内容是:[%s].\n", buf);
    
    exit(0);
}

3.6.3.并发式IO(重要!)的解决方案

(1)也可以使用fork创建子进程,父进程执行一个,子进程执行一个。

3.6.3.1、非阻塞式IO(这个demo使用的是非阻塞式的。使用while(1)循环,使程序一直再读内容,使用步骤是先打开文件,使用 O_NONBLOCK设定文件描述符为非阻塞方式,鼠标是在open文件时,设置权限,键盘是使用fcntl函数。

为什么用并发式IO:是为了解决阻塞式IO的困境,阻塞式IO的困境在于必须按照程序的顺序执行,否则将会阻塞在那里。用户体验极差。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
/*
    *由阻塞变为非阻塞文件的操作。
*/
int main(int argc, char** argv)
{
/*
    //先打开鼠标文件
    int fd = -1;
    char buf[100] = {0};
    int flag = -1;
    fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    //将0号文件,就是通用输入文件描述符变为非阻塞式的。
    flag = fcntl(0, F_GETFL);    //先获取原来的文件描述符状态。
    flag |= O_NONBLOCK;             //使用位或,添加非阻塞属性。
    fcntl(0, F_SETFL, flag);     //更新状态,第三个参数表明需要设置的值。
    //这三部将0号文件变为非阻塞文件
    
    while(1)         //在while中读取这些文件。
    {
        int ret = -1;
        memset(buf, 0, sizeof(buf));
        ret = read(fd, buf, 10);
        if(ret > 0)
        {
            printf("鼠标输出的内容是:[%s]/\n", buf);
        }
        
        memset(buf, 0, sizeof(buf));
        ret = read(0, buf, 10);
        if(ret > 0)
        {
            printf("键盘输入的内容为[%s].\n", buf);
        }
        
    }
*/
    
/*
    //读取鼠标
    int fd = -1;
    char buf[200];
    //打开文件                        //O_NONBLOCK变为非阻塞的,
    fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
    if(fd < 0)
    {
        perror("open");
        exit(0);
    }
    //读文件
    memset(buf, 0, sizeof(buf));
    printf("打印之前before  read\n");
    read(fd, buf, 10);
    printf("读出的内容是:[%s].\n",buf);
*/
    
    
    /*
    
    //读取键盘,  键盘就是标准的输入,stdin,0..因为这个文件已经打开了,所以不需要open
    
    int flag = -1;
    char buf[100] = {0};
    
    //把0号文件描述符变成非阻塞式的
    flag = fcntl(0, F_GETFL);   //先获取原来的文件标志
    flag |= O_NONBLOCK;         //添加非阻塞属性
    fcntl(0, F_SETFL, flag);    //更新flag
    //经过这三步之后,文件就变成了非阻塞式的了
    
    memset(buf, 0, sizeof(buf));
    printf("before  read.\n");
    read(0, buf, 10);
    printf("读出的内容为:【%s】\n", buf);//直接读下来,即使没有内容也没有阻塞。
    */
//在父子进程同时执行这个阻塞式IO
    pid_t pid = -1;
    pid = fork();
    
    if(pid >0)
    {
        //父进程
    int fd = -1;
    char buf[200];
    //打开文件                        //O_NONBLOCK变为非阻塞的,
    fd = open("/dev/input/mouse1", O_RDONLY );
    if(fd < 0)
    {
        perror("open");
        exit(0);
    }
    //读文件
    memset(buf, 0, sizeof(buf));
    printf("打印之前before  read\n");
    read(fd, buf, 10);
    printf("读出的内容是:[%s].\n",buf);
    }
    else if(pid == 0)
    {
        //子进程
        //读取键盘,  键盘就是标准的输入,stdin,0..因为这个文件已经打开了,所以不需要open
    
    int flag = -1;
    char buf[100] = {0};
    
    //把0号文件描述符变成非阻塞式的
    //flag = fcntl(0, F_GETFL);   //先获取原来的文件标志
    //flag |= O_NONBLOCK;         //添加非阻塞属性
    //fcntl(0, F_SETFL, flag);    //更新flag
    //经过这三步之后,文件就变成了非阻塞式的了
    
    memset(buf, 0, sizeof(buf));
    printf("before  read.\n");
    read(0, buf, 10);
    printf("读出的内容为:【%s】\n", buf);//直接读下来,即使没有内容也没有阻塞。
    }
    
    else
    {
        //错误
        printf("fork出现错误\n");
    }
    
    exit(0);
}

3.6.3.2、多路复用IO
3.6.3.3、异步通知(异步IO)

3.6.4.IO多路复用原理

3.6.4.1、何为IO多路复用:就像一个多路开关,一开始先阻塞在那里,然后哪一路需要使用,就用开关导通那一路。

(1)IO multiplexing

(2)用在什么地方?多路非阻塞式IO。

(3)select(类似监视函数,)和poll(这两个函数本身是阻塞式的,内部实现机理:其内部也是以自动轮询的方式来实现的,非阻塞的形式。)

(4)外部阻塞式,内部非阻塞式自动轮询多路阻塞式IO。
while(1) 是以浪费CPU的使用权为代价的,sleep()是以在睡眠期间交出CPU的使用权,二者使用情景不同。

3.6.4.2、select( 线程不安全的 )函数介绍:int  FD_ISSET(int fd, fd_set *set);   将fd放到第二个参数里面去测试,如果返回值是正的,就表示这路IO发生了。void FD_SET(int fd, fd_set *set);  将文件描述符添加到集合中。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <string.h>
/*
int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
*/
int main(int argc, char** argv)
{
    //打开鼠标的文件
    int fd = -1, ret = -1;
    char buf[200] = {0};
    fd_set myset;                //是一long类型的数组
    struct timeval timeout;
    
    fd = open("/dev/input/mouse0", O_RDONLY);
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    //当前有两个fd,一个0一个fd
    FD_ZERO(&myset);            //清空集合
    FD_SET(0, &myset);
    FD_SET(fd, &myset);
    timeout.tv_sec = 10;
    timeout.tv_usec = 0;
    
    ret = select(fd+1, &myset, NULL, NULL, &timeout);    //第一个参数是文件描述符的作用。因为文件描述符是从0开始的,所以最大文件描述符+1
    if(ret < 0)
    {
        perror("select");
        exit(-1);
    }
    else if(ret == 0)
    {
        printf("超时了\n");
    }
    else
    {
        
        //等到了一路IO,然后去检测是哪一个IO,去处理
        if(FD_ISSET(0, &myset))
        {
            //这里处理键盘
            memset(buf, 0 , sizeof(buf));
            ret = read(0, buf, 10);
            if(ret < 0)
            {
                perror("read");
                exit(-1);
            }
            printf("键盘读出的内容是:[%s]\n", buf);
        }
        if(FD_ISSET(fd, &myset))
        {
            //在这里处理鼠标
            memset(buf, 0 , sizeof(buf));
            ret = read(fd, buf, 10);
            if(ret < 0)
            {
                perror("read");
                exit(-1);
            }
            printf("键盘读出的内容是:[%s]\n", buf);
        }
        
    }
    
    close(fd);
    close(0);
    
    exit(0);
}

3.6.4.3、poll( 常用,同样线程不安全 )函数介绍: 要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞(可以同时读写)。 第二个参数 nfds 制定数组中元素个数(文件描述符都是从0开始的)。第三个参数指定 poll 函数返回前等待多长时间。第一个参数是一个结构体数组,需要设置fd,和events事件
结构体数组: 结构体数组是指能够存放多个结构体类型的一种数组形式。
# include < sys/ poll. h>
struct pollfd {
int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 实际发生了的事件 */
} ; 
pselect,ppoll允许应用安全的等待文件描述符准备好或捕捉到一个信号

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>
/*
struct pollfd
{
    int   fd;         
    short events;     
    short revents;    
};
*/
int main(int argc, char** argv)
{
    /*
    struct pollfd myfd[2] = {0};       //结构体数组的使用,每一个数组里面都有一个结构体类型。
    myfd[0].fd = 2;
    myfd[0].events = 3;
    myfd[0].revents = 4;
    myfd[1].fd = 2;
    myfd[1].events = 3;
    myfd[1].revents = 4;
    int i = 0;            
    for(i = 0; i<2; i++)
    {
        printf("%d\n",myfd[i].fd);
        printf("%d\n",myfd[i].events);
        printf("%d\n",myfd[i].revents);
    }
*/
    //读取鼠标
    int fd = -1, ret = -1;
    char buf[200] = {0};
    struct pollfd myfds[2] = {0};        //结构体数组
    
    fd = open("/dev/input/mouse0", O_RDONLY);//鼠标、、鼠标打开文件调整一下。
    if(fd < 0)
    {
        perror("open");
        exit(-1);
    }
    
    //初始化我们的pollfd
    myfds[0].fd = 0;        //键盘
    myfds[0].events = POLLIN;  //等待读操作。
    
    myfds[1].fd = fd;        //鼠标
    myfds[1].events = POLLIN;  //等待读操作。
    
    ret = poll(myfds, fd+1, 10000);        //因为文件描述符是从0开始的。
    if(ret < 0)
    {
        perror("poll");
        exit(-1);
    }
    else if(ret == 0)
    {
        printf("超时了\n");
    }
    else
    {
        int i;
        for(i = 0; i<2; i++)
        {
            //等到了一路IO,然后去判断到底是哪个IO到了,去处理
            if(myfds[i].events == myfds[i].revents)
            {
                //鼠标到了处理鼠标,键盘到了处理键盘。
                memset(buf, 0, sizeof(buf));
                ret = read(i? fd : 0, buf, 10);
                if(ret <0 )
                {
                    perror("read");
                    exit(-1);
                }
                printf("鼠标读到的内容是[%s]\n", i? buf:"无数据");
                printf("键盘读到的内容是[%s]\n", i? "无数据":buf);
            }
        }
    }
    exit(0);
}

3.6.5.IO多路复用实践(网络通信,常用。)
3.6.5.1、用select函数实现同时读取键盘鼠标
3.6.5.2、用poll函数实现同时读取键盘鼠标

3.6.6.异步IO

3.6.6.1、何为异步IO
(1)几乎可以认为:异步IO就是操作系统用软件实现的一套中断响应系统。

(2)异步IO的工作方法是:我们当前进程注册一个异步IO事件(使用signal注册一个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到一个SIGIO信号从而执行绑定的处理函数去处理这个异步事件。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <string.h>
int fdmouse = -1;
//绑定到SIGIO信号,在函数内处理异步通知事件。异步IO
void func(int sig)
{
    char buf[200] = {0};
    
    if(sig != SIGIO)
            exit(-1);
    read(fdmouse, buf, 10);
    printf("鼠标读到的内容是:[%s]\n",buf);
}
int main(int argc, char** argv)
{
    //读取鼠标
    char buf[200] ={0};
    int flag = -1;
    fdmouse = open("/dev/input/mouse1", O_RDONLY);
    if(fdmouse < 0)
    {
        perror("open");
        exit(-1);
    }
    
    //把鼠标的文件描述符设置为可以接受异步的IO
    flag = fcntl(fdmouse, F_GETFL); //获得当前文件描述符的状态
    flag |= O_ASYNC;
    fcntl(fdmouse, F_SETFL, flag);   //重新设置文件描述符,添加一个接受O_ASYNC
    
    //把异步IO事件的接受进程,设置为当前进程。
    fcntl(fdmouse, F_SETOWN, getpid());                                    //F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。
    
    //注册当前进程的SIGIO信号的捕获函数
    signal(SIGIO, func);    //自动将信号的number(number是8)传给func
    //printf("鼠标读到的内容是:[%s]\n",buf);
    
        //正常情况下执行键盘的读取,将读取鼠标注册为异步IO后,发生读取鼠标事件,则去读取鼠标。类似中断。
#if 1    
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        //printf("before 键盘 read.\n");
        read(0, buf, 5);
        printf("键盘读出的内容是:[%s].\n", buf);
        
    }
#endif
    exit(0);
}

3.6.6.2、涉及的函数:*( 必须有其他进程正在执行,程序没停止,才可以进行这种中断响应。
(1)fcntl(F_GETFL、F_SETFL、O_ASYNC、F_SETOWN)
F_SETOWN:设置将要在文件描述词fd上接收SIGIO 或 SIGURG事件信号的进程或进程组标识 。 

(2)signal或者sigaction(SIGIO)

3.6.3.代码实践

3.6.7.存储映射IO

3.6.7.1、mmap(内存映射,是共享,而不是复制。一块内存是另一块的影子,指向同一块物理内存。)函数:man手册,文件和内存对应起来。

3.6.7.2、LCD显示和IPC(进程间通信)之共享内存

3.6.7.3、存储映射IO的特点
(1)共享而不是复制,减少内存操作
(2)处理大文件时效率高(譬如图像,图像是连续的),小文件不划算


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值