文档章节

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

fzyz_sb
 fzyz_sb
发布于 2013/10/03 15:59
字数 1791
阅读 72
收藏 2
点赞 0
评论 0

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的第五章,第六章和第八章.这三章所讲的内容实在是太丰富了.


© 著作权归作者所有

共有 人打赏支持
fzyz_sb
粉丝 404
博文 209
码字总数 447144
作品 0
武汉
程序员
【flex&bison翻译】前言

译者注:去年的时候曾经计划翻译本书,后来终于还是抵不过懒惰,给放下了,有句经典的话:现在的努力,是为了小时候吹过的牛逼。现在体会深刻啊。。。本文是在Ubuntu 12.04.1系统下,使用Lib...

MatthewChie ⋅ 2012/08/25 ⋅ 2

计算机书籍目录

计算机系统与网络 《图灵的秘密》 《计算机系统概论》 《深入理解Linux内核》 《深入Linux内核架构》 《TCP/IP详解 卷1:协议》 《Linux系统编程(第2版)》 《Linux内核设计与实现(第3版)...

Reborn-D ⋅ 2016/11/01 ⋅ 0

史上最全面的C语言的学习路线及方法

UNIX下C语言的学习路线。 工具篇 “公欲善其事,必先利其器”。编程是一门实践性很强的工作,在你以后的学习或工作中,你将常常会与以下工具打交道, 下面列出学习C语言编程常常用到的软件和工...

stone15165 ⋅ 04/18 ⋅ 0

如何学好C语言?

  C语言杂谈 如何学好C语言?为什么会有学的既不深,也不扎实,半吊子的感觉      我相信,这可能是很多朋友的问题,我以前也有这样的感觉,编程编到一定的时候,发现能力到了瓶颈,既...

编程大亨 ⋅ 2017/12/20 ⋅ 0

经典编程书籍大全

经典编程书籍大全 100+ 经典技术书籍,涵盖:计算机系统与网络、系统架构、算法与数据结构、前端开发、后端开发、移动开发、数据库、测试、项目与团队、程序员职业修炼、求职面试 和 编程相关...

Oscarfff ⋅ 2016/10/30 ⋅ 0

如何学好C语言?为什么会有学的既不深,也不扎实,半吊子的感觉

如何学好C语言?为什么会有学的既不深,也不扎实,半吊子的感觉 C/C++学习,解答,技术内容更多尽在:C/C++学习群:99816772 我相信,这可能是很多朋友的问题,我以前也有这样的感觉,编程编...

这个人很懒什么都没留下 ⋅ 2017/12/20 ⋅ 0

如何学好C语言

我相信,这可能是很多朋友的问题,我以前也有这样的感觉,编程编到一定的时候,发现能力到了瓶颈,既不深,也不扎实,半吊子。比如:你长期地使用Java和.NET ,这些有虚拟机的语言对于开发便...

邪恶的小Y ⋅ 2011/08/16 ⋅ 2

shell是什么语言?shell 语言的本质

shell是什么语言?shell 语言的本质 “Shell是Linux/Unix的一个外壳,你理解成衣服也行。它负责外界与Linux内核的交互,接收用户或其他应用程序的命令,然后把这些命令转化成内核能理解的语言...

dhb_oschina ⋅ 2016/04/27 ⋅ 0

程序员经典书籍清单

从网上看到的,还不错。 来源: https://github.com/jobbole/awesome-programming-books 计算机系统与网络 《图灵的秘密:他的生平、思想及论文解读》 《计算机系统概论》 《深入理解Linux内核...

wangtaotao ⋅ 2016/12/27 ⋅ 0

Linux命令与shell

资料来自:《http://blog.chinaunix.net/uid-14880649-id-2954340.html》 所谓shell就是命令解释程序。它提供了程序设计接口,可以使用程序来编程。学习shell对于Linux初学者理解Linux系统是...

hiqj ⋅ 2015/09/07 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从 Confluence 5.3 及其早期版本中恢复空间

如果你需要从 Confluence 5.3 及其早期版本中的导出文件恢复到晚于 Confluence 5.3 的 Confluence 中的话。你可以使用临时的 Confluence 空间安装,然后将这个 Confluence 安装实例升级到你现...

honeymose ⋅ 今天 ⋅ 0

用ZBLOG2.3博客写读书笔记网站能创造今日头条的辉煌吗?

最近两年,著名的自媒体网站今日头条可以说是火得一塌糊涂,虽然从目前来看也遇到了一点瓶颈,毕竟发展到了一定的规模,继续增长就更加难了,但如今的今日头条规模和流量已经非常大了。 我们...

原创小博客 ⋅ 今天 ⋅ 0

MyBatis四大核心概念

本文讲解 MyBatis 四大核心概念(SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper)。 MyBatis 作为互联网数据库映射工具界的“上古神器”,训有四大“神兽”,谓之:Sql...

waylau ⋅ 今天 ⋅ 0

以太坊java开发包web3j简介

web3j(org.web3j)是Java版本的以太坊JSON RPC接口协议封装实现,如果需要将你的Java应用或安卓应用接入以太坊,或者希望用java开发一个钱包应用,那么用web3j就对了。 web3j的功能相当完整...

汇智网教程 ⋅ 今天 ⋅ 0

2个线程交替打印100以内的数字

重点提示: 线程的本质上只是一个壳子,真正的逻辑其实在“竞态条件”中。 举个例子,比如本题中的打印,那么在竞态条件中,我只需要一个方法即可; 假如我的需求是2个线程,一个+1,一个-1,...

Germmy ⋅ 今天 ⋅ 0

Springboot2 之 Spring Data Redis 实现消息队列——发布/订阅模式

一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式,这里利用redis消息“发布/订阅”来简单实现订阅者模式。 实现之前先过过 redis 发布订阅的一些基础概念和操...

Simonton ⋅ 今天 ⋅ 0

error:Could not find gradle

一.更新Android Studio后打开Project,报如下错误: Error: Could not find com.android.tools.build:gradle:2.2.1. Searched in the following locations: file:/D:/software/android/andro......

Yao--靠自己 ⋅ 昨天 ⋅ 0

Spring boot 项目打包及引入本地jar包

Spring Boot 项目打包以及引入本地Jar包 [TOC] 上篇文章提到 Maven 项目添加本地jar包的三种方式 ,本篇文章记录下在实际项目中的应用。 spring boot 打包方式 我们知道,传统应用可以将程序...

Os_yxguang ⋅ 昨天 ⋅ 0

常见数据结构(二)-树(二叉树,红黑树,B树)

本文介绍数据结构中几种常见的树:二分查找树,2-3树,红黑树,B树 写在前面 本文所有图片均截图自coursera上普林斯顿的课程《Algorithms, Part I》中的Slides 相关命题的证明可参考《算法(第...

浮躁的码农 ⋅ 昨天 ⋅ 0

android -------- 混淆打包报错 (warning - InnerClass ...)

最近做Android混淆打包遇到一些问题,Android Sdutio 3.1 版本打包的 错误如下: Android studio warning - InnerClass annotations are missing corresponding EnclosingMember annotation......

切切歆语 ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部