• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

[C程序设计语言]第五部分

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
 
 

UNIX系统接口

文件描述符


UNIX操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件,因此,所有的输入/输出都要通过读文件或写文件完成。
    因为大多数的输入/输出是通过键盘和显示器来实现的,为了方便起见,UNIX对此做了特别的安排。当命令解释程序(即“shell”)运行一个程序的时候,它将打开3个文件,对应的文件描述符分别为0, 1, 2,依次表示标准输入,标准输出和标准错误。如果程序从文件0中读,对12进行写,就可以进行输/输出而不必关心打开文件的问题。
    程序的使用者可通过<>重定向程序的I/O:
    prog <输入文件名 >输出文件名
这种情况下,shell把文件描述符0l的默认赋值改变为指定的文件。通常,文件描述符2仍与显示器相关联,这样,出错信息会输出到显示器上。在任何悄况下,文件赋值的改变都不是程序完成的,而是由shell完成的。只要程序使用文件0作为输入,文件l2作为输出,它就不会知程序的输入从哪里来,并输出到哪里去。

底层I/O readwrite


    输入与输出是通过现readwrite系统调用实现的。在C语言程序中,可以通过函数readwrite访问这两个系统调用。这两个函数中,第一个参数是文件描述符,第二个参数是程序中存放读和写的数据的字符数组,第三个参数是要传输的字节数:
int n_read = read(int fd, char *buf, int n);
int n_written = write(int fd, char *buf, int n);
每个调用返回实际传输(读或写)的字节数。在读文件时,函数的返回值可能会小于请求的字节数,如果返回值为0,则表示已到达文件的结尾;如果返回值为-1,则表示发生了某种错误。在写文件时,返回值是实际写入的字节数,如果返回值与请求写入的字节数不相等,则说明发生了错误
    在一次调用中,读出或写入的数据的字节数可以为任意大小。最常用的值为1,即每次读出或写入1个字符(无缓冲),或是类似于1024~4096这样的与外围设备的物理块大小相应的值。用更大的值调用该函数可以获得更高的效率,因为系统调用的次数减少了。
#include"syscalls.h"
main() /*将输入复制到输出*/
{
    char buf[BUFSIZ];
    int n;
    while ((n = read(0, buf, BUFSIZ)) > 0)
       write(1, buf, n);
    return 0;
}
本章中的程序都将包含该头文件syscalls.h不过,该文件的名字不是标准的。参数BUFSIZ已经在syscalls.h头文件中定义了。对于所使用的操作系统来说,该值是一个较合适的数值。如果文件大小不是BUFSIZ的倍数,则对read的某次调用会返回一个较小的字节数,write再按这个字节数写。
 
构造类似于getchar高级函数: 从标准输入读入一个字符来实现无缓冲输入(第一个版本)
#include"syscalls.h"
int getchar(void) {
    char c;
    return (read(0, &c, 1) == 1) ? (unsignedchar) c : EOF;
}
第二个版本:一次读入一组字符,但每次只输出一个字符
#include"syscalls.h"
int getchar(void) {
    staticchar buf[BUFSIZ];
    staticchar *bufp = buf;
    staticint n = 0;
    if (n == 0) { /* buffer is empty */
       n = read(0, buf, sizeof buf);
       bufp = buf;
    }
    return (n >= 0) ? (unsignedchar) *bufp++ : EOF;
}
如果要在包含头文件始<stdio.h>的情况下编译这些版本的getchar函数,就有必要用#-undef预处理指令取消名字getchar的宏定义,因为在头文件中,getchar是以宏方式实现的

open, creat, closeunlink


    除了默认的标准输入、标准输出和标准错误文件外,其它文件都必须在读或写之前显式地打开。系统调用opencreat用于实现该功能。
    open与前面讨论的fopen相似,不同的是,前者返回一个文件描述符,它仅仅只是一个int类型的数值。而后者返回一个文件指针。如果发生错误,open将返回-l
#include <fcntl.h>
int fd;
int open(char *name, int flags, int perms);
fd = open(name, flags, perms);
fopen一样,参数name是一个包含文件名的字符串,第二个参数flags是一个int类型的值,它说明以何种方式打开文件,主要的几个值如下所示:
    O_RDONLY                只读方式打开文件
     O_WRONLY               只写方式打开文件
    O_RDWR          读写方式打开文件
 在这里讨论的open第三个参数始终为0
 
使用create创建或覆盖已存在的旧文件:
int creat(char *name, int perms);
fd = creat(name, perms);
如果creat成功地创建了文件,它将返回一个文件描述符,否则返回-1。如果此文件已存在,creat将把该文件的长度截断为0,从而丢弃原先己有的内容使用creat创建一个已存在的文件不会导致错误
如果要创建的文件不存在,则creat用参数pems指定的权限创建文件。在UNIX文件系统中,每个文件对应一个9比特的权限信息,它们分别控制文件的所有者、所有者组和其他成员对文件的读、写和执行访问。因此,通过一个3位的八进制数就可方便地说明不同的权限,例如,0755(八进制)说明文件的所有者可以对它进行读、写和执行操作,而所有者组和其他成员只能进行读和执行操作。
 
下面通过一个简化UNIX程序cp说明creat的用法,该程序将一个文件复制到另一个文件。目标文件的权限不是通过复制获得的,而是重新定义的:
#include<stdio.h>
#include<fcntl.h>
#include<stdarg.h>
#include"syscalls.h"
#define PERMS 0666 /* RW for owner, group, others */
void error(char *, ...);
/* cp: copy f1 to f2 */
main(int argc, char *argv[]) {
    int f1, f2, n;
    char buf[BUFSIZ];
    if (argc != 3)
       error("Usage: cp from to");
    if ((f1 = open(argv[1], O_RDONLY, 0)) == 1)
       error("cp: can't open %s", argv[1]);
    if ((f2 = creat(argv[2], PERMS)) == 1)
       error("cp: can't create %s, mode %03o", argv[2], PERMS);
    while ((n = read(f1, buf, BUFSIZ)) > 0)
       if (write(f2, buf, n) != n)
           error("cp: write error on file %s", argv[2]);
    return 0;
}
void error(char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    fprintf(stderr, "error: ");
    vprintf(stderr, fmt, args);
    fprintf(stderr, "\n");
    va_end(args);
    exit(1);
}
该程序创建的输出文件具有固定的权限0666,也可使用stat系统调用,可以获得一个已存在文件的模式,并将此模式赋值给它的副本。标准库函数vprintf数与printf函数类似,所不同的是,它用一个参数取代了变长参数表,因此参数通过调用va_start宏进行初始化。同样,vfprintfvsprintf函数分别与fprintfsprintf函数类似。
 
函数close (int fd)用来断开文件描述符和已打开文件之间的连接,并释放此文件描述符,以供其它文件使用。close函数与标准库中的fclose函数相对应,但它不需要清除(flush)缓冲区。
 
函数unlink(char *name)将文件name从文件系统中删除,它对应于标准库函数remove

随机访问-lseek


系统调用lseek可以在文件中任意移动位置而不实际读写任何数据
    long lseek(int fd, long offset, int origin);
文件描述符为fd的文件的当前位置设置为offset,其中,offset是相对于orgin指定的位置而言的。随后进行的读写操作将从此位置开始,origin的值可以为012,分别用于指定offset从文件开始、从当前位置或从文件结束处开始算起。例如,为了向一个文件的尾部添加内容(在UNIX shell程序中使用重定向符>>或在系统调用fopen中使用参数“a”):
    lseek(fd, 0L, 2);
 
#include"syscalls.h"
/*get: read n bytes from position pos 从文件的任意位置读入任意数目的字节,它返回读入的字节数,若发生错误返回-1*/
int get(int fd, long pos, char *buf, int n) {
    if (lseek(fd, pos, 0) >= 0) /* get to pos 先移动到指定的位置*/
       returnread(fd, buf, n); /* 再读取*/
    else
       return 1;
}
 
lseek系统调用返回long类型的值,此值表示文件的新位置,若发生错误,则返回-1。标准库函数fseek与系统调用lseek类似,不同的是,前者第一个参数为FILE *类型,并且发生错误时返回一个非0值。

实例-fopengetc函数的实现


标准库中的文件不是通过文件描述符描述的,而是使用文件指针描述的文件指针是一个指向包含文件各种信息的结构的指针,该结构包含下列内容:一个指向缓冲区的指针,通过它可以一次读入文件的一大块内容;一个记录缓冲区中剩余的字符数的计数器;一个指向缓冲区中下一个字符的指针;文件描述符;描述读/写模式的标志;描述错误状态的标志等。
——相关宏定义——
#define NULL 0
#define EOF (1)
#define BUFSIZ 1024
#define OPEN_MAX 20 /* 一次允许打开的最大文件数 */
typedefstruct _iobuf {
    intcnt; /* 剩余字符数 */
    char *ptr; /* 下一个字符的位置 */
    char *base; /* 缓冲区的位置 */
    intflag; /* 文件访问模式与出错标示 */
    intfd; /* 文件描述符 */
} FILE;
 
externFILE _iob[OPEN_MAX]; //用来存储打开的文件结构数据
#define stdin (&_iob[0]) //第一个用作标准输入
#define stdout (&_iob[1]) //第二个用作标准输出
#define stderr (&_iob[2]) //第三个用作错误输出
enum _flags {
    /*  使用了5位来标示文件的访问方式与文件访问时出错标示: 00 011 111
       以位来标示便于使用位运算符&来判断文件的打开后所处的状态,如判断打开
       文件是否出错:(fp -> flag & (_EOF | _ERR)) != 0 */
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
C#访问MySql数据库(通过MySql.Data)发布时间:2022-07-14
下一篇:
C#多线程的阻塞和继续发布时间:2022-07-14
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap