C程序设计语言--第八章:UNIX系统接口

UNIX操作系统通过一系列的系统调用提供服务,这些系统调用实际上是操作系统内的函数,它们可以被用户程序调用.

8.1 文件描述符

在UNIX操作系统中,所有的外围设备(包括键盘和显示器)都被看作是文件系统中的文件,因此,所有的输入/输出都要通过读文件或写文件完成.也就是说,通过一个单一的接口就可以处理外围设备和程序之间的所有通信.

通常情况下,在读或写文件之前,必须先将这个意图通知系统,该过程称为打开文件.系统检查你的权利(该文件是否存在?是否有访问它的权限?),如果一切正常,操作系统将向程序返回一个小的非负整数,该整数称为文件描述符.任何时候对文件的输入/输出都是通过文件描述符标识文件,而不是通过文件名标识文件.系统负责维护已打开文件的所有信息,用户程序只能通过文件描述符引用文件.

因为大多数的输入/输出是通过键盘和显示器来实现的.为了方便起见,UNIX对此作了特别的安排.当命令解释程序(shell)运行一个程序的时候,它将打开3个文件,对应的文件描述符分别是0,1,2,依次标识标准输入,标准输出和标准错误.如果程序从文件0中读,对1和2进行写,就可以进行输入/输出而不必关心打开文件的问题.

程序的使用者可以通过<和>重定向程序的I/O.

prog < infile > outfile
8.2 低级I/O----read和write
int n_read = read( int fd, char *buf, int n );
int n_written = write( int fd, char *buf, int n );
这两个函数中,第一个参数是文件描述符,第二个参数是程序中存放读或写的数据的字符数组,第三个参数是要传输的字节数.每次调用返回实际传输的字节数.在读文件时,函数的返回值如果为0,则表示已到达文件的末尾;如果返回值为-1,则表示发生了某种错误.在写文件时,返回值是实际写入的字节数.如果返回值与请求写入的字节数不相等,则说明发生了错误.

通过read和write来写一个输入复制到输出的程序:

#include <stdio.h>

#define BUFSIZE 20
int main( void )
{
	char buf[ BUFSIZE ];
	int n;
	memset( buf, 0, BUFSIZE );

	while ( ( n = read( 0, buf, BUFSIZE ) ) > 0 ){
		write( 1, buf, BUFSIZE );
	}

	return 0;
}
之所以成为低级I/O,是因为上面的程序会老老实实的读取BUFSIZE,无论你输入的是多少.例如:

所以,一般情况下写程序是不会用到read和write的.

而getchar则可以通过read来实现:

1. 无缓冲的单字符输入

int getchar( void )
{
	char c;
	return ( read( 0, &c, 1 ) == 1 ) ? ( unsigned char ) c : EOF;
}
其中,c必须是一个char类型的变量,因为read函数需要一个字符指针类型的参数(&c).在返回语句中将c转换为unsigned char类型可以消除符号扩展问题.

2. 简单的带缓冲区的版本

#define BUFSIZE 128

int getchar( void )
{
	static char buf[ BUFSIZE ];
	static char *bufp = buf;
	static int n = 0;

	if ( n == 0 ){
		n = read( 0, buf, sizeof( buf ) );
		bufp = buf;
	}
	return ( --n >= 0 ) ? ( unsigned char ) *bufp++ : EOF;
}
可能有些人会问程序中bufp的作用是什么,为什么不能直接用buf.细心的会发现,如果用buf,则*bufp++需要改为buf[ i++ ],需要引入一个static int i.这就是指针的魅力所在.

8.3 open,creat,close和unlink

除了默认的标准输入,标准输出和标准错误文件外,其他文件都必须在读或写之前显式的打开.系统调用open和creat用于实现该功能.

open返回一个文件描述符,如果发生错误,open将返回-1.

#include <fcntl.h>
int fd;
int open( char *name, int flags, int perms );
fd = open( name, flags, perms );
参数name是一个包含文件名的字符串,flags是一个int类型的值,它说明以何种方式打开文件:
O_RDONLY	只读方式
O_WRONLY	只写方式
O_RDWR		读写方式
perms代表权限.

就目前的UNIX系统中,creat的方法已经不推荐使用了.之前之所以存在是因为用open打开一个不存在的文件会发生错误,但现在打开一个不存在的文件时候,会自动创建这个文件.

我们来编写一个简化版的UNIX程序cp:

#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#define PERMS 0666
#define BUFSIZE 128

void error( char *, ... );

int main( int argc, char *argv[] )
{
	int f1, f2, n;
	char buf[ BUFSIZE ];

	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, BUFSIZE ) ) > 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: ");
	vfprintf( stderr, fmt, args );
	fprintf( stderr, "\n" );
	va_end( args );
	exit( 1 );
}
函数unlink( char *name )将文件name从文件系统中删除,它对应于标准库函数remove.

习题8-1:

#include <stdio.h>
#include <fcntl.h>
#include <stdarg.h>
#define BUFSIZE 128

void error( char *, ... );

int main( int argc, char *argv[] )
{
	int fd;
	void filecopy( int ifd, int ofd );

	if ( argc == 1 ){
		filecopy( 0, 1 );
	}
	else{
		while ( --argc > 0 ){
			if ( ( fd = open( *++argv, O_RDONLY ) ) == -1 ){
				error("cat: can't open %s", *argv );
			}
			else{
				filecopy( fd, 1 );
				close( fd );
			}
		}
	}

	return 0;
}

void filecopy( int ifd, int ofd )
{
	int n;
	char buf[ BUFSIZE ];

	while ( ( n = read( ifd, buf, BUFSIZE ) ) > 0 ){
		if ( write( ofd, buf, n ) != n ){
			error( "cat: write error" );
		}
	}
}

void error( char *fmt, ... )
{
	va_list args;

	va_start( args, fmt );
	fprintf(stderr, "error: ");
	vfprintf( stderr, fmt, args );
	fprintf( stderr, "\n" );
	va_end( args );
	exit( 1 );
}
8.4 随机访问----lseek

输入/输出通常是顺序进行的:每次调用read和write进行读写的位置紧跟在前一次操作的位置之后.但是,有时候需要以任意顺序访问文件,系统调用lseek可以在文件中任意移动位置而不是及读写任何数据:

long lseek( int fd, long offset, int origin );
将文件描述符为fd的文件的当前位置设置为offset,其中,offset是相对于origin指定的位置而言.origin的值可以为0,1或2,分别用于指定offset从文件开始,从但钱位置或从文件结束处开始算起.

使用lseek系统调用时,可以将文件视为一个大数组,其代价是访问速度会慢一些.下面的函数将从文件的任意位置读入任意数目的字节,它返回读入的字节数,若发生错误,则返回-1:

int get( int fd, long pos, char *buf, int n )
{
	if ( lseek( fd, pos, 0 ) >= 0 ){
		return read( fd, buf, n );
	}
	else{
		return -1;
	}
}

由于对UNIX不太熟悉,所以8.5,8.6,8.7看得不太懂,故无法做任何的笔记.先放着.

等对C语言有了进一步的认识后,再回头看看K&R的第五章,第六章和第八章.这三章所讲的内容实在是太丰富了.


转载于:https://my.oschina.net/voler/blog/165772

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值