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

当执行./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不需要加\n)

结果:

还有一个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\n";

 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就是传出参数

猜你喜欢

转载自www.cnblogs.com/eret9616/p/10828920.html