Sunday, July 11, 2010

IO笔记

IO部分总是很乱。不知道是因为API多还是其它原因:
 0. 阻塞的存在
 1. glibc的section3手册和linux的section2有同名函数。
 2. posix, ISO都有要求
 3. terminal IO与其它IO有不同的表现
 4. IO缓存的机制
 5. 还有众多古老的名词....

EOF        
EOF是什么? 什么时候才会产生? 还是和ctrl_c一样是signal ?

如下代码的运行結果是 -1
 19 #include <stdio.h>
 20
 21 int
 22 main(int argc, char *argv[])
 23 {
 24     printf("%d\n", EOF);
 25     return 0;
 26     
 27 } 

表示EOF是 -1 ?

当某一次使用 ssize_t read(int fd, void *buf, size_t count); 函数从一个file descriptor中读数据时(count>0), 函数得到0个字节表示读到文件的尾部了. 这时read函数返回0.

可读端的pipe关闭后, 只读端pipe在读完后会产生EOF:
  2 import os
  3 import sys
  4 import time
  5
  6 read_end, write_end = os.pipe()
  7
  8 pid = os.fork()
  9 if pid < 0:
 10     print "Error"
 11     sys.exit(1)
 12 if pid == 0:
 13     # parent
 14     os.close(write_end)
 15     try_time = 1
 16     # 因为无法得到child process会发多少数据, 只能使用死循环去读数据
 17     while True:
 18         print "%s time(s) call read" % try_time
 19         content = os.read(read_end, 1)
 20         if not content:
 21             break
 22         os.write(1, content)
 23         try_time += 1
 24     sys.exit(0)
 25
 26 else:
 27     # child
 28     os.close(read_end)
 29     os.write(write_end, "#" * 100)
 30     # 关了parent就能读到EOF
 31     os.close(write_end)
 32     sys.exit(0)



文件系统中的文件是有固定大小(某一状态), 但是在terminal IO里,是没有一个固定的尾部的, 需要输入者指定什么时候才是end-of-file, 所以, ctrl_D出现了. ctrl_D是一个特殊控制符。被tty的驱动处理(默认情况下用户程序不能从read函数得到, 除非要求tty不处理)

在了解ctrl_D前, 应该先要了解IO的缓存机制.

standard IO缓存 与 底层IO缓存
在此这前, 我对getchar这个函数不解, 其实就是不解如下的代码:
 19 #include <stdio.h>
 20
 21 int main(int argc, char *argv[]){
 22     char c ;
 23     while(c = getchar() ){
 24        printf("%c\n", c);
 25     }  
 26 }  

不要以为简单调用getchar函数可以实现vim这种交互.
当调用read去读stdin的数据时, 是行缓存的.上面的代码在第一次调用getchar时把一行的数据读到buffer里(回车键返回), 下次再调用getchar时会从buffer里取. buffer没有数据后再等待用户输入.

标准IO与系统底层IO这两套IO需要分开. 不然会出现事与愿违的情况, 如:
使用setbuf对FILE对象进行了设置后, 然后使用调用printf操作. 結果没有出现想要的设置效果。例如:

 23 #include <stdio.h>
 24 #include <string.h>
 25 #include <unistd.h>
 26
 27
 28 int main(int argc, char *argv[]){
 29     //
 30     FILE *input =  fdopen(1, "r");
 31     FILE *output =  fdopen(1, "w");
 32     setbuf(input, NULL);
 33     setbuf(output, NULL);
 34     char c ;
 35
 36     printf("Hello World");
 37     sleep(10);
 38 }

上面的printf的内容是过了10秒后才打印的, 明显, setbuf没有起作用。 但是如果修改为如下就不同了:
 23 #include <stdio.h>
 24 #include <string.h>
 25 #include <unistd.h>
 26
 27
 28 int main(int argc, char *argv[]){
 29     //
 30     FILE *input =  fdopen(1, "r");
 31     FILE *output =  fdopen(1, "w");
 32     setbuf(input, NULL);
 33     setbuf(output, NULL);
 34     char c ;
 35
 36     fprintf(output, "Hello World");
 37     sleep(10);
 38 }
上面代码的区别在于:printf使用了stdin这个FILE对象(默认是line buffer), fprintf使用了指定的FILE对象。standard IO的缓存是通过修改FILE对象实现的。

read和write会不会缓存, 缓存机制是什么? 这与read, write操作的对象有很大的关系, 例如:

 28 int main(int argc, char *argv[]){
 29
 30     while(1){
 31         char c;
 32         (void *)read(0, &c, 1);
 33         (void *)write(1, &c, 1);
 34     }
 35 }
上面虽然使用了read, write, 还是用户输入还是行缓存的, 无法像vim一样交互操作。这时要想非行缓存, 需要操作到tty的缓存机制。 如下的一段python代码设置了tty的缓存方式:

  1 #coding:utf-8
  2 import os
  3 import sys
  4 import termios
  5
  6 STDIN_FILENO = sys.stdin.fileno()
  7
  8 old_attr = termios.tcgetattr(STDIN_FILENO)
  9 new_attr = termios.tcgetattr(STDIN_FILENO)
 10
 11 new_attr[3] &= ~ (termios.ICANON | termios.ECHO)
 12 termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, new_attr)
 13 try:
 14     print '请使用vim的移动键'
 15     while True:
 16         c= os.read(STDIN_FILENO, 1)
 17         if c == 'j':
 18             print "下"
 19         elif c == 'k':
 20             print "上"
 21         elif c == 'h':
 22             print '左'
 23         elif c == 'l':
 24             print '右'
 25 except KeyboardInterrupt, e:
 26     termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, old_attr)


回到ctrl_D的问题
在terminal IO里, 因为默认是使用缓存的, 赋于ctrl_D两种功能:
 1. 相当于flush操作.  即时把输入的数据返回给read函数, 不用等待'\n'的出现。
 2. 让tty返回0字节给read函数. 表示EOF。 在单独输入ctrl_D后产生这种效果

控制符        
控制符都是tty驱动提供的一种方便功能, 有少量的控制符会产生signal的:
^Z和^C
还有少量的是无法修改的:
\r和\n

如下代码把“a“作为backspace功能:

  1 #coding:utf-8
  2 import os
  3 import sys
  4 import termios
  5
  6 STDIN_FILENO = sys.stdin.fileno()
  7
  8 old_attr = termios.tcgetattr(STDIN_FILENO)
  9 new_attr = termios.tcgetattr(STDIN_FILENO)
 10
 11 new_attr[6][termios.VERASE] = 0x61
 12 termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, new_attr)
 13 try:
 14     print "请使用a键删除数据"
 15     os.read(0,100)
 16 except KeyboardInterrupt, e:
 17     termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, old_attr)


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.