zoukankan      html  css  js  c++  java
  • 04-Linux系统编程-第01天(文件IO、阻塞非阻塞)

    03-系统函数

    系统编程章节大纲

    1 文件I/O

    2 文件系统

    3 进程

    4 进程间通信

    5 信号

    6 进程间关系

    7 守护进程

    8 线程

    9 线程同步

    10 网络基础

    11 socket编程

    12 高并发服务器

    13 异步I/O

    14 shell编程

     

    Man page

    1 命令和可执行程序

    2 系统调用

    3 标准库

    系统调用本质就是函数

    man 2 printf

    man

    严格来说write是 sys_write的浅封装

    sys_write才是真正的系统调用 不过一般我们就说write是系统调用

    内核:操作系统内部核心程序

    内核里包含一些驱动程序

    printf 然后  系统调用write函数 驱动显卡输出

    int 返回一个文件描述符 

    vim里查看man手册 K

    光标移动到open 然后 2 K

     

    fcntl.h  定义O_RDONLY宏

    unistd.h   声明open系统调用

    stdio.h 声明printf

    只有当第二个参数为O_CREAT的时候 才需要在第三个参数指定权限

    (上面意思是 读 如果没有就创建)

    最终创建出的文件的权限受第三个参数决定和umask一起决定

    直接给结论:mode(第三个参数) & ~umask

    mode 与  (umask取反)

    比如umask是002 那么umask取反就是775

    775与777 就是775

    以只读方式打开 如果文件不存在就创建 如果文件已经存在就截断

    (截断:把文件里内容清除掉)

    文件描述符返回-1就是错了

    04-read_write

    想明白文件描述符本质

    要先明白PCB

    .bss: 未初始化的全局变量 初始化为0的全局变量 static修饰(静态变量)的未初始化的或初始化为0的变量

    data:初始化为非0的全局变量 static修饰(静态变量)的初始化为非0的变量

    rodata:常量、只读全局变量

    stack:局部变量


    段错误:

    1. 对只读区域进程写操作。 char *p = "hello"; p[0] = 'H';

    2. 对非访问区域进行读写操。 地址1000

    3. stack空间耗尽。 函数内部 int arr[N]= {1}; #define N 100000000

    当执行./a.out的时候操作系统   会虚拟出这样的虚拟地址

    一个进程可能实际只使用几k 。 但是可用地址范围有这么多

    pcb 本质是一个结构体

    里面有一个成员变量是指针

    指针指向一个指针数组(可以理解为一个字符指针数组 这个数组里面都是指针)

    (int *p [N]) 

    每一个指针指向字符串 严谨的来说 指向的还是结构体

    (… 不明觉厉)

    数组下标就理解为文件描述符

    (实际上是一个指针指向结构体)

    open一个文件时 内核会维护一个结构体让我操作该文件,

    man 2 read

    read和write函数的使用:

    read和write函数的使用:

    05-cp命令实现

     

    copy.c:

    #include <fcntl.h>

    #include <unistd.h>

    #include <errno.h>

    int main(int argc,char * argv[])

    {

       char buf[1024];

       int ret = 0;

       int fd1 = open(argv[1],O_RDONLY);

       int fd2 = open(argv[2],O_RDWR |O_TRUNC|O_CREAT,0664);

       while((ret = read(fd1,buf,sizeof(buf))) != 0 )

       {

           write(fd2,buf,ret);

       }

      close(fd1);

      close(fd2);

    }

    06-预读入缓输出

    每当执行一个a.out的时候 在kernal里就会有一个与之唯一对应的PCB进程控制块

    注意:

    read 返回的是实际读到的字节数

    write的时候 第三个参数应该传这个字节数 而不是像read一样是buf的大小

    C标库一次读一个字符

    #include <stdio.h>

    #include <stdlib.h>

    int main(void)

    {

      FILE *fp,*fp_out;

      int n;

      fp = fopen(“dict.txt”,”r”);

      if(fp == NULL) {

         perror(“fopen error”);

         exit(1)

      }

      fp_out = fopen(“dict.cp”,”w”);

      if(fp == NULL) {

         perror(“fopen error”);

         exit(1);

    }

    while((n = fgetc(fp))!=EOF) {

       fputc(n,fp_out);

    }

    fclose(fp);

    fclose(fp_out);

    return 0;

    }

    系统调用一次读一个字符:

    #include <stdlib.h>

    #include <errno.h>

    #define N 1

    int main(int argc,char * argv[])

    {

      int fd,fd_out;

      int n;

      char buf[N];

    fd = open(“dict.txt”,O_RDONLY);

    if(fd < 0){

      perror(“open dict.txt error”);

      exit(1);

    }

    fd_out = open(“dict.cp”,O_WRONLY|O_CREAT|O_TRUNC,0644);

    if(fd<0){

       perror(“open dict.cp error”);

       exit(1);

    }

    close(fd);

    close(fd_out);

    return 0;

    }

    实际跑了一遍发现用read函数一个字节一个字节读还不如用fgetc速度块..

    用系统调用不仅没快 反而奇慢

    知识点:

    预读入缓输出

    预读入缓输出机制

    我们认为用户程序用户定义buf直接使用系统调用write函数 跳过了标准库函数进入kernal

    让kernal写到磁盘文件上了

    实际上不是这样的:

    实际上在内核中默认也维护了一个缓冲区 默认是4096byte

    内核为了提高效率会一次性等缓冲区满以后再刷到磁盘上

    当自己写的write函数写的时候 实际上没有写到磁盘上,而是在内核的缓冲区

    这种机制称为缓输出

     

    C标准库函数 自己带着一个缓冲区

    fgetc fputc。。。 这些内部实现包含缓冲区

    fgetc读字符会放到自己的缓冲区里

    读4096字节放到自己的蓝色框缓冲区里

    然后向下系统调用 write

    把数据写到内核的缓冲区

    然后再刷到磁盘上

    之所以慢是因为

    从用户区切换到内核区这个时间消耗很大。

    直接系统调用write的方法 从用户区到内核区的切换工作耗了很多时间 每次一个字节就要切换一次

    用fgetc fputc 只切换了一次

     

    下面验证一下…:

    strace 命令跟踪程序运行时间的系统调用

     

    结果: 每次读写字节都切换

     

    结果:

    每次4096个 总的切换次数少很多

    预读入:

    一次性把缓冲区读满

    需要的时候就从缓冲区取

    既然效率这么低 那么学习系统调用write函数有啥用 直接用标库函数不就得了..?

    答案: 比如qq聊天 要求即时性, 不是要等4096个byte再发过去

    emm 把刚才write函数的define N 1 改成 1024 就能发现快很多了

    把数据交给内核 内核什么时间写到磁盘上由内核决定 交给它内部的I/O优化算法。

    07-错误处理函数

    建议对所有的系统调用做错误处理

    不使用printf打印错误了

    使用perror

     (perror不需要加 )

    结果:

    还有一个strerror(不常用

     

    08-阻塞非阻塞

    终端设备是指: 0 标准输入 1 标准输出 3 标准错误

    当cat的时候就阻塞了

    stdin stdout stderror对应的文件都是设备文件 dev下面的tty

    下面看三个小程序

    *

    读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

    现在明确一下阻塞(Block)这个概念。当进程调用一个阻塞的系统函数时,该进程被置于睡眠(Sleep)状态,这时内核调度其它进程运行,直到该进程等待的事件发生了(比如网络上接收到数据包,或者调用sleep指定的睡眠时间到了)它才有可能继续运行。与睡眠状态相对的是运行(Running)状态,在Linux内核中,处于运行状态的进程分为两种情况:

    正在被调度执行。CPU处于该进程的上下文环境中,程序计数器(eip)里保存着该进程的指令地址,通用寄存器里保存着该进程运算过程的中间结果,正在执行该进程的指令,正在读写该进程的地址空间。

    就绪状态。该进程不需要等待什么事件发生,随时都可以执行,但CPU暂时还在执行另一个进程,所以该进程在一个就绪队列中等待被内核调度。系统中可能同时有多个就绪的进程,那么该调度谁执行呢?内核的调度算法是基于优先级和时间片的,而且会根据每个进程的运行情况动态调整它的优先级和时间片,让每个进程都能比较公平地得到机会执行,同时要兼顾用户体验,不能让和用户交互的进程响应太慢。

    阻塞读终端:                          【block_readtty.c】

    非阻塞读终端                          【nonblock_readtty.c】

    非阻塞读终端和等待超时         【nonblock_timeout.c】

    注意,阻塞与非阻塞是对于文件而言的。而不是read、write等的属性。read终端,默认阻塞读。

    总结read 函数返回值:  

    1. 返回非零值:  实际read到的字节数

    2. 返回-1:  1):errno != EAGAIN (或!= EWOULDBLOCK)  read出错

                         2):errno == EAGAIN (或== EWOULDBLOCK)  设置了非阻塞读,并且没有数据到达。

    3. 返回0:读到文件末尾

    1.阻塞读终端:

    block_readtty.c:

    2.非阻塞读终端::

    nonblock_readtty.c

    正常情况下./a.out tty是默认打开的 但是非阻塞的情况下 重新打开了tty文件 目的是给他指定以O_NONBLOCK非阻塞方式打开终端

    定义tryagain标签,后面有goto语句

    read函数返回小于0 (即-1) 时候已经出错了 但是其实出错了还可以进一步进行判断errorno

    如果errno是 EAGAIN或=EWOULDBLOCK 说明当前是以非阻塞的方式读终端 而恰巧终端没有数据

    如果errno不是EAGAIN说明是出错了

    如果是EAGAIN说明read函数没有出错 只不过读的文件是一个设备文件而当前没有数据递达 所以就sleep了3秒

    非阻塞读终端 每隔3秒弹出try again

    3.非阻塞读终端 等待超时

    nonblack_timeout.c:

    man 2 read

    正常情况下read函数返回的是你实际读到的字节数

    09-lseek

    标准库里讲过fseek 设置文件的读写位置

    linux中可以使用lseek

      

    lseek.c:

    有个问题 当我write完之后 光标指向结尾了 这个时候read不出来了

    所以要使用lseek

    把指针再指向开头

    lseek(fd,0,SEEK_SET)

    注意:一个空文件lseek位置以后必须进行一下i/o操作 才会发生实质性的拓展 否则lseek没啥用

    (如果不write就没啥用了

    vi  查看一下生成的文件

     

    前面这些填充我们称为 文件空洞 其实就是0

    应该用od看 不是vi

    od –tcx lseek.txt

    lseek也可以用来获取文件大小

    int len = lseek(fd,0,SEEK_END) // 这个就是文件大小

      1 #include <string.h>

      2 #include <fcntl.h>

      3 #include <stdio.h>

      4 #include <stdlib.h>

      5 #include <unistd.h>

      6

      7

      8

      9 int main(void)

     10 {

     11         int fd,n;

     12         char msg[] = "It's a test for lseek ";

     13         char ch;

     14

     15

     16         fd = open("lseek.txt",O_RDWR|O_CREAT|O_TRUNC,0644);

     17

     18         if(fd<0){

     19                 perror("open lseek.txt error");

     20                 exit(1);

     21         }

     22

     23         int ret = lseek(fd,99,SEEK_SET);

     24         if(ret == -1){

     25                 perror("lseek error");

     26                 exit(1);

     27         }

     28         write(fd,"a",1);

     29

     30         close(fd);

     31

     32         return 0;

    10-fcntl

    fcontrol

    文件描述符对应着一个结构体 结构体内部控制着访问属性

    (man 2 fcntl

    fgetfileflag 获取当前文件信息

    fsetfileflag 设置当前文件信息

       位或  或等于

    位图

    bitmap

    当描述一个文件属性的时候 通过一个整形数的二进制位来描述

    i/o control

    11-ioctl和传入传出参数

    io control

    iocntl.c:

     

    TIOCGWINSZ

    terminal IO windowsize

    虚拟终端窗口大小

    locate sys/ioctl.h

    sudo grep –r “TIOCGWINSZ” /usr

    传出参数 这个函数调用一结束 size结构体就有值了:

    通过io control 拿到当前窗口占用的行值和列值

    获取当前设备文件的行宽和列宽

    举例strcpy(char *dest, const char *src)

    *dest就是传出参数

  • 相关阅读:
    Android杂谈ubuntu系统下adb连接小米2
    Android UI设计ListView的页脚(footer)的使用
    Android杂谈关于Android的nodpi,xhdpi,hdpi,mdpi,ldpi
    ”该证书已被签发机构吊销“错误解决方案
    Android杂谈RelativeLayout中的baseline是什么?
    ubuntu下git更改默认编辑器
    Android UI设计ListView Item的OnItemLongClickListener同时监听两种事件
    Android UI设计ListView的item选中效果
    Ubuntu下ssh服务器文件操作命令
    ubuntu下emacs的配置(cedit,ecb)
  • 原文地址:https://www.cnblogs.com/eret9616/p/10828920.html
Copyright © 2011-2022 走看看