Linux IO编程

一、标准IO

(一) 文件基础

1. 概念

一组相关数据的有序集合

2. 文件的类型
  • 常规文件 r

最常使用的一类文件,其特点是不包含有文件系统信息的结构信息。通常用户所接触到的文件,比如图形文件、数据文件、文档文件以及声音文件都属于这种文件,这种类型的文件是按照其内部结构又可分为纯文本文件(ASCII)、二进制文件(binary)、数据格式的文件(data)、各种压缩文件。

  • 纯文本文件(ASCII):这是Unix系统中最多的一种文件类型,之所以称为纯文本文件,是因为内容可以直接读到的数据,例如数字、字母等等。设 置文件几乎都属于这种文件类型。举例来说,使用命令“cat ~/.bashrc”就可以看到该文件的内容(cat是将文件内容读出来)。

  • 二进制文件(binary):系统其实仅认识且可以执行二进制文件(binary file)。Linux中的可执行文件(脚本,文本方式的批处理文件不算)就是这种格式的。举例来说,命令cat就是一个二进制文件。

  • 数据格式的文件(data):有些程序在运行过程中,会读取某些特定格式的文件,那些特定格式的文件可以称为数据文件(data file)。举例来说,Linux在用户登入时,都会将登录数据记录在 /var/log/wtmp文件内,该文件是一个数据文件,它能通过last命令读出来。但使用cat时,会读出乱码。因为它是属于一种特殊格式的文件。

  • 目录文件 d

用于存放文件名以及其相关信息的文件,是内核组织文件系统的基本节点。目录文件可以包含下一级文件目录或者普通文件,在Linux中,目录文件是一种文件。

能用 # cd 命令进入的。

  • 字符设备文件 c

字符设备文件:即串行端口的接口设备,例如键盘、鼠标等等。

  • 块设备文件 b

块设备文件 : 就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件。

  • 管道文件 p

是一种很特殊的文件,主要用于不同进程的信息传递。当两个进程需要进行数据或者信息传递时,可以使用通道文件,一个进程将需要传递的数据或者信息写入管道的一端,另一进程从管道的另一端取得所需要的数据或者信息,通常管道是建立在调整缓存中

  • 套接字文件 s

这类文件通常用在网络数据连接。可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信。

  • 链接文件类型 I

是一种特殊文件,指向一个真实存在的文件链接,类似于Windows下的快捷方式,链接文件的不同,又可分为硬链接文件和符号链接文件。

(1)、查看文件类型的三种方法

①**ls -l/ls -ld 或者ll [ls -l —查看文件 ls -ld —查看路径`` ll ----跟ls -l 一样]**

ll anaconda-ks.cfg                       //看第一个字符
​
-rw-------. 1 root root 2460 6月   1 23:37 anaconda-ks.cfg
​
[root@localhost log]# ls -ld /etc   
​
drwxr-xr-x. 81 root root 4096 Jan 29 03:25 /etc

②**file 命令**

[root@localhost data]# file a.txt 
​
a.txt: ASCII text

③**stat 命令**

[root@localhost data]# stat a.txt      //查看文件的详细属性(其中包括文件时间属性)
​
  File: `a.txt'
​
  Size: 3               Blocks: 8          IO Block: 4096   regular file
​
Device: 803h/2051d      Inode: 544365      Links: 1
​
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
​
Access: 2018-01-28 20:56:01.965885036 +0800
​
Modify: 2018-01-28 20:55:27.181876154 +0800
​
Change: 2018-01-28 20:55:27.181876154 +0800
3、Linux中文件扩展名

windows里通过扩展名来区分文件类型的。linux里文件扩展名和文件类型没有关系。但为了容易区分和兼容用户使用windows的习惯,我们还是会用扩展名来表示文件类型。举例如下:

  • 源码.tar、.tar.gz、.tgz、.zip、.tar.bz表示压缩文件,创建命令一般为tar,gzip,zip等。

  • .sh表示shell脚本文件,通过shell语言开发的程序。

  • .pl表示perl语言文件,通过perl语言开发的程序。

  • .py表示python语言文件,通过python语言开发的程序。

  • .html、.htm、.php、.jsp、.do表示网页语言的文件。

  • .conf表示系统服务的配置文件。

  • .rpm表示rpm安装包文件。

4、文件的属性

 

inode 索引节点编号:544365 文件类型 :文件类型是’-’,表示这是一个普通文件 文件权限:rw-r–r-- 表示文件可读、可写、可执行,文件所归属的用户组可读可执行,其他用户可读可执行 硬链接个数 表示a.txt这个文件没有其他的硬链接,因为连接数是1,就是他本身 文件属主 表示这个文件所属的用户,这里的意思是a.txt文件被root用户拥有,是第一个root 文件属组 表示这个文件所属的用户组,这里表示a.txt文件属于root用户组,是第二个root 文件大小 文件大小是3个字节 文件修改时间 这里的时间是该文件最后被更新(包括文件创建、内容更新、文件名更新等)的时间可用如下命令查看文件的修改、访问、创建时间

5、 更改文件属性
(1)chgrp:更改文件属组

语法:

chgrp [-R] 属组名 文件名

参数选项

  • -R:递归更改文件属组,就是在更改某个目录文件的属组时,如果加上-R的参数,那么该目录下的所有文件的属组都会更改。

(2)chown:更改文件属主,也可以同时更改文件属组

语法:

chown [–R] 属主名 文件名
chown [-R] 属主名:属组名 文件名

进入 /root 目录(~)将install.log的拥有者改为bin这个账号:

[root@www ~] cd ~
[root@www ~]# chown bin install.log
[root@www ~]# ls -l
-rw-r--r--  1 bin  users 68495 Jun 25 08:53 install.log

将install.log的拥有者与群组改回为root:

[root@www ~]# chown root:root install.log
[root@www ~]# ls -l
-rw-r--r--  1 root root 68495 Jun 25 08:53 install.log
(3)chmod:更改文件9个属性

Linux文件属性有两种设置方法,一种是数字,一种是符号。

Linux 文件的基本权限就有九个,分别是 owner/group/others(拥有者/组/其他) 三种身份各有自己的 read/write/execute 权限。

先复习一下刚刚上面提到的数据:文件的权限字符为: -rwxrwxrwx , 这九个权限是三个三个一组的!其中,我们可以使用数字来代表各个权限,各权限的分数对照表如下:

  • r:4

  • w:2

  • x:1

每种身份(owner/group/others)各自的三个权限(r/w/x)分数是需要累加的,例如当权限为: -rwxrwx--- 分数则是:

  • owner = rwx = 4+2+1 = 7

  • group = rwx = 4+2+1 = 7

  • others= --- = 0+0+0 = 0

所以等一下我们设定权限的变更时,该文件的权限数字就是 770。变更权限的指令 chmod 的语法是这样的:

 chmod [-R] xyz 文件或目录

选项与参数:

  • xyz : 就是刚刚提到的数字类型的权限属性,为 rwx 属性数值的相加。

  • -R : 进行递归(recursive)的持续变更,以及连同次目录下的所有文件都会变更

举例来说,如果要将 .bashrc 这个文件所有的权限都设定启用,那么命令如下:

[root@www ~]# ls -al .bashrc
-rw-r--r--  1 root root 395 Jul  4 11:45 .bashrc
[root@www ~]# chmod 777 .bashrc
[root@www ~]# ls -al .bashrc
-rwxrwxrwx  1 root root 395 Jul  4 11:45 .bashrc

那如果要将权限变成 -rwxr-xr-- 呢?那么权限的分数就成为 4+2+1[4+0+0]=754。

(4)符号类型改变文件权限

还有一个改变权限的方法,从之前的介绍中我们可以发现,基本上就九个权限分别是:

  • user:用户

  • group:组

  • others:其他

那么我们就可以使用 u, g, o 来代表三种身份的权限。

此外, a 则代表 all,即全部的身份。读写的权限可以写成 r, w, x,也就是可以使用下表的方式来看:

chmodu+(加入) -(除去) =(设定)r文件或目录
gw
ox
a

如果我们需要将文件权限设置为 -rwxr-xr-- ,可以使用 chmod u=rwx,g=rx,o=r 文件名 来设定:

#  touch test1    // 创建 test1 文件
# ls -al test1    // 查看 test1 默认权限
-rw-r--r-- 1 root root 0 Nov 15 10:32 test1
# chmod u=rwx,g=rx,o=r  test1    // 修改 test1 权限
# ls -al test1
-rwxr-xr-- 1 root root 0 Nov 15 10:32 test1

而如果是要将权限去掉而不改变其他已存在的权限呢?例如要拿掉全部人的可执行权限,则:

#  chmod  a-x test1
# ls -al test1
-rw-r--r-- 1 root root 0 Nov 15 10:32 test1

(二) 标准I/O -介绍

  • IO:input/output,针对文件的输入输出。

1.概念:
  • 在C库中定义的一组专门用于输入输出的函数。

2.特点:
    (1)标准IO通过缓冲机制减少系统调用的次数,从而提高效率。

    (2)标准IO围绕“流”进行操作,流使用“ FILE * ”描述,“ FILE * ”->一个结构体指针。

    (3)标准IO默认打开三个流:stdin(标准输入),stdout(标准输出),stderr(标准出错)。

系统调用:内核向上提供的一层函数接口。
3.ctags追踪代码

例:查看FILE的代码。

命令:在终端输入vi -t FILE

回车后进入以下界面后,输入1,然后回车:

回车后进入以下界面:

光标自动位于48行开头,可以看出FILE是结构体_IO_FILE重定义后的结构体名;

然后将光标置于_IO_FILE处,按下"Ctrl+]",终端底部将会出现以下界面:

输入1后回车,就可以看到有关结构体_IO_FILE的详细代码:

Ctrl+t可以回退到上一界面;

(三) 标准I/O -流

1. FILE

标准IO用一个结构体类型来存放打开的文件的相关信息 标准I/O的所有操作都是围绕FILE来进行

2. 流(stream)

FILE又被称为流(stream) 文本流/二进制流

(四) 标准I/O - 文本流和二进制流

1. Windows

二进制流: 换行符   ‘\n’ 文本流: 换行符   ‘\r’ ‘\n

2. Linux

换行符 ‘\n’

(五) 标准I/O - 流的缓冲类型

1.全缓冲

全缓存与文件相关,刷新缓存区有三个条件:

①程序正常退出时刷新;

②缓存区满刷新;

③强制刷新:fflush函数;

2. 行缓冲

行缓存与终端相关,刷新行缓存有四个条件:

‘\n’可以刷新;

②程序正常退出时刷新;

③缓存区满刷新;

④强制刷新:fflush函数;

3. 无缓冲

数据直接写入文件, 流不进行缓冲

不缓存即没有缓存区,一般是*标准出错stderr*,因为程序出错时,我们希望它及时显示在终端上,而不是保存在缓存区;

4. 示例

分析以下代码:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	printf("hello");
	while(1);
	return 0;
}
// 程序运行后,光标一直停止在行首闪烁,不会输出字符串hello

我们尝试三种方式来刷新缓存区:

(1)使用\n刷新缓存区:
#include <stdio.h>

int main(int argc, const char *argv[])
{
	printf("hello\n");
	while(1);
	return 0;
}

程序运行后,会先输出字符串hello,然后光标在字符串的下一行行首不断闪烁;

(2)缓存区满刷新:
#include <stdio.h>
int main(int argc, const char *argv[])
{
	for(int i = 0;i < 300;i++)
	{
		printf("%04d",i);
	}
	while(1);
	return 0;
}

我们可以通过缓存区满刷新的特点来计算缓存区的大小,程序执行后,每输出一个数字,该数字都占4位,不够4位以0补全,从0输出到255,一共输出了256个数字,缓存区大小即256*4,1024个字符大小,但是这种方法比较麻烦;

(3) 通过标准IO计算缓存区大小:

使用标准IO计算缓存区大小时,应该先使用缓存区,然后再计算;

#include <stdio.h>

int main(int argc, const char *argv[])
{
    printf("hello\n");//必须先使用缓存区,然后进行缓存区大小计算
	printf("%d\n",stdout->_IO_buf_end - stdout->_IO_buf_base);
	return 0;
}
  • stdout相当于FILE *,也就是FILE的结构体指针,buf指缓冲区;

  • 程序执行后,先输出字符串hello,然后在下一行输出缓存区大小1024;

(4)强制刷新fflush:
 #include <stdio.h>

int main(int argc, const char *argv[])
{
	printf("hello");
	fflush(NULL);
	while(1);
	return 0;
}   

程序执行后,先输出hello,然后光标在hello的下一位持续闪烁;

(六) 标准I/O - stdin,stdout,stderr

标准I/O预定义3个流, 程序运行时自动打开

标准输入流0STDIN_FILENOstdin
标准输出流1STDOUT_FILENOstdout
·标准错误流2STDERR_FILENOstderr

(七)标准I/O - fopen函数

1. 标准I/O - 打开流

下列函数可用于打开一个标准I/O流:

FILE *fopen (const char *path, const char *mode);

成功时返回流指针; 出错时返回NULL

2. 标准I/O - fopen - mode参数

mode参数:

“r” 或 “rb”以只读方式打开文件,文件必须存在
“r+” 或 ”r+b”以读写方式打开文件, 文件必须存在。
“w” 或 “wb”以只写方式打开文件, 若文件存在则文件长度清为0。 若文件 不存在则创建。
“w+” 或 “w+b”以读写方式打开文件, 其他同”w”。
“a” 或 “ab”以只写方式打开文件, 若文件不存在则创建; 向文件写入的数 据被追加到文件末尾。
“a+” 或 “a+b”以读写方式打开文件。 其他同”a”

* 当给定”b”参数时, 表示以二进制方式打开文件, 但Linux下忽略该参数

 

3. 标准I/O -fopen - 示例
#include <stdio.h>
int main(int argc, char *argv[])
{
    FILE *fp;
    if ((fp = fopen("test.txt", "r+")) == NULL) {
        printf("fopen error\n");
        return -1;
}
	return 0;
}
4. 标准I/O - freopen - 示例
#include <stdio.h>
int main(int argc,  char *argv[])
{
   if (freopen("test.txt", "w", stdout) == NULL) {
       perror("freopen");
       return -1;
   }
   printf("hello world\n");
   return 0; 
}

4. 标准I/O - fopen - 新建文件权限
  • fopen() 创建的文件访问权限是0666(rw-rw-rw-)

  • Linux系统中umask设定会影响文件的访问权限, 其规则 为(0666 & ~umask)

  • 用户可以通过umask函数修改相关设定

  • 如果希望umask不影响文件访问权限, 该如何设定?

(八) 标准I/O - 处理错误信息

1. 处理错误信息相关的函数
extern int errno;
void perror(const char *s);
char *strerror(int errno);
  • errno 存放错误号

  • perror先输出字符串s, 再输出错误号对应的错误信息

  • strerror根据错误号返回对应的错误信息

2. 标准I/O - 错误信息处理 - 示例1
#include <stdio.h>
int main(int argc, char *argv[])
{
    FILE *fp;
    if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
        perror(“fopen”);
        return -1;
} …
…
fopen: No such file or director
    
3. 标准I/O - 错误信息处理 - 实例2
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main(int argc, char *argv[])
{
    FILE *fp;
    if ((fp = fopen(“test.txt”, “r+”)) == NULL) {
        printf(“fopen: %s\n”, strerror(errno));
        return -1;
} …
…
fopen: No such file or directory

(九) 标准I/O - 关闭流

int fclose(FILE *stream);

  • fclose()调用成功返回0, 失败返回EOF, 并设置errno

  • 流关闭时自动刷新缓冲中的数据并释放缓冲区

  • 当一个程序正常终止时, 所有打开的流都会被关闭。

  • 流一旦关闭后就不能执行任何操作

(十) 标准I/O - 文件的读写

1. 标准I/O - 读写流

流支持不同的读写方式:

  • 读写一个字符: fgetc()/fputc()一次读/写一个字符

  • 读写一行: fgets()和fputs()一次读/写一行

  • 读写若干个对象: fread()/fwrite() 每次读/写若干个对象, 而每个对象具有相同的长度

2. 标准I/O - 按字符输入
(1) 标准I/O - 按字符输入

下列函数用来输入一个字符:

#include <stdio.h> int fgetc(FILE *stream); int getc(FILE *stream); int getchar(void);

  • 成功时返回读取的字符; 若到文件末尾或出错时返回EOF

  • getchar()等同于fgetc(stdin)

(2) 标准I/O - fgetc - 示例
int ch;
ch = fgetc(stdin);
printf(“%c\n”, ch);
FILE *fp;
int ch, count = 0;
if ((fp = fopen(argv[1], “r”)) == NULL) {
	perror(“fopen”); return -1;
} 
while ((ch = fgetc(fp)) != EOF) {
	count++;
} 
printf(“total %d bytes\n”, count);
3. 标准I/O - 按字符输出
(1) 标准I/O - 按字符输出

下列函数用来输出一个字符:

#include <stdio.h> int fputc(int c, FILE *stream); int putc(int c, FILE *stream); int putchar(int c);

  • 成功时返回写入的字符; 出错时返回EOF

  • putchar(c)等同于fputc(c, stdout)

(2) 标准I/O – fputc – 示例
fputc(‘a’, stdout);
putchar(‘\n’);
FILE *fp;
int ch;
if ((fp = fopen(argv[1], “w”)) == NULL) {
	perror(“fopen”); return -1;
} 
for(ch = ‘a’; ch <=‘z’; ch++) {
	fputc(ch, fp);
}
4 . 案例 - 复制文件
#include <stdio.h>
int main(int argc,  char *argv[])
{
   FILE *fps, *fpd;
   int ch;
   
   if (argc < 3) {
      printf("Usage : %s <src_file> <dst_file>\n", argv[0]);
	  return -1;
   }
 
   if ((fps = fopen(argv[1], "r")) == NULL) {
       perror("fopen");
	   return -1;
   }
   
   if ((fpd = fopen(argv[2], "w")) == NULL) {
       perror("fopen");
	   return -1;
   }
   
   while ((ch = fgetc(fps)) != EOF) {
      fputc(ch, fpd);
   }
   fclose(fps);
   fclose(fpd);

   return  0;
 }
5. 标准I/O - 按行输入
(1) 标准I/O - 按行输入

下列函数用来输入一行:

#include <stdio.h> char *gets(char *s); char *fgets(char *s, int size, FILE *stream);

  • 成功时返回s, 到文件末尾或出错时返回NULL

  • gets不推荐使用, 容易造成缓冲区溢出

  • 遇到’\n’或已输入size-1个字符时返回, 总是包含’\0’

(2) 标准I/O - fgets - 示例
#define N 6
char buf[N];
fgets(buf, N, stdin);
printf(“%s”, buf);
  • 假设键盘输入分别是: abcd<回车> abcdef<回车>

  • buf中的内容是?

6. 标准I/O - 按行输出
(1) 标准I/O - 按行输出

下列函数用来输出字符串:

#include <stdio.h> int puts(const char *s); int fputs(const char *s, FILE *stream);

  • 成功时返回输出的字符个数; 出错时返回EOF

  • puts将缓冲区s中的字符串输出到stdout, 并追加’\n’

  • fputs将缓冲区s中的字符串输出到stream

(2) 标准I/O - fputs - 示例
puts(“hello world”);
FILE *fp;
char buf[] = “hello world”;
if ((fp = fopen(argv[1], “a”)) == NULL) {
	perror(“fopen”);
	return -1;
} 
fputs(buf, fp);
  • 注意: 输出的字符串中可以包含’\n’, 也可以不包含

7. 案例 - 统计文本文件行数
#include <stdio.h>
#include <string.h>

#define  N  64

int main(int argc,  char *argv[])
{
   FILE *fp;
   int line = 0;
   char buf[N];

   if (argc < 2) {
      printf("Usage: %s <file>\n", argv[0]);
	  return -1;
   }
   
   if ((fp = fopen(argv[1], "r")) == NULL) {
      printf("fopen  error\n");
      return -1;
   }
   
   while (fgets(buf, N, fp) != NULL) {
      if (buf[strlen(buf)-1] == '\n') line++;
   }

   printf("the line of %s is %d\n", argv[1], line);
   
   return  0;
 }
8. 标准I/O - 按对象读写
(1) 标准I/O - 按对象读写

下列函数用来从流中读写若干个对象:

#include <stdio.h> size_t fread(void *ptr, size_t size, size_t n, FILE *fp); size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);

  • 成功返回读写的对象个数; 出错时返回EOF

  • 既可以读写文本文件, 也可以读写数据文件

  • 效率高

(2) 标准I/O – fread/fwrite – 示例
int s[10];
if (fread(s, sizeof(int), 10, fp) < 0) {
	perror(“fread”);
	return -1;
} 
struct student {
	int no;
	char name[8];
	float score;
}
s[] = {{ 1, “zhang”, 97}, {2, “wang”, 95}};
fwrite(s, sizeof(struct student), 2, fp);
9. 标准I/O - 刷新流
(1) 标准I/O - 刷新流

#include <stdio.h> int fflush(FILE *fp);

  • 成功时返回0; 出错时返回EOF

  • 将流缓冲区中的数据写入实际的文件

  • Linux下只能刷新输出缓冲区

10 . 标准I/O - 定位流 – ftell/fseek/rewind
(1) 标准I/O - 定位流 – ftell/fseek/rewind

#include <stdio.h> int fflush(FILE *fp);

  • 成功时返回0; 出错时返回EOF

  • 将流缓冲区中的数据写入实际的文件

  • Linux下只能刷新输出缓冲区

  • offset参数: 偏移量, 可正可负

  • rewind()将流定位到文件开始位置

  • 读写流时, 当前读写位置自动后移

(2) 示例一(在文件末尾追加字符’t’ )
FILE *fp = fopen(“test.txt”, “r+”);
fseek(fp, 0, SEEK_END);
fputc(‘t’, fp);
(3) 示例二 (获取文件长度))
#include <stdio.h>
int main(int argc,  char *argv[])
{
   FILE *fp;
   if ((fp = fopen("test.txt", "r+")) == NULL) {
       perror("fopen");
       return -1;
   }
   fseek(fp, 0, SEEK_END);
   printf("length is %ld\n", ftell(fp));   

   return  0;
 }
11. 标准IO - 判断流是否出错和结束
(1) 标准IO - 判断流是否出错和结束

#include <stdio.h> int ferror(FILE *stream); int feof(FILE *stream);

  • ferror()返回1表示流出错; 否则返回0

  • feof()返回1表示文件已到末尾; 否则返回0

12. 标准I/O - 格式化输出
(1) 标准I/O - 格式化输出

#include <stdio.h> int printf(const char *fmt, …); int fprintf(FILE *stream, const char *fmt, …); int sprintf(char *s, const char *fmt, …);

  • 成功时返回输出的字符个数; 出错时返回EOF

  • 使用起来很方便, 强烈推荐!

(2) 标准I/O - 格式化输出 - 示例1

以指定格式 “年-月-日” 分别写入文件和缓冲区

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

int main()
{
   FILE *fp;
   int line = 0;
   char buf[64];
   time_t t;
   struct tm *tp;
   
   if ((fp = fopen("test.txt", "a+")) == NULL) {
       perror("fopen");
       return -1;
   }
   while (fgets(buf, 64, fp) != NULL)
   {
      if (buf[strlen(buf)-1] == '\n') line++;
   }   
   while ( 1 )
   {
      time(&t);
      tp = localtime(&t);
      fprintf(fp, "%02d, %d-%02d-%02d %02d:%02d:%02d\n", ++line, tp->tm_year+1900, tp->tm_mon+1,
                                tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec);
      fflush(fp);
      sleep(1);
   }	  
   return  0;
 }
(3)标准I/O - 格式化输出 - 示例2

格式化输出示例

#include <stdio.h>

int main() 
{
   int  year, month, date;
   FILE *fp;
   char buf[64];

   year = 2014; month = 10; date = 26;
   fp = fopen("test.txt", "a+");
   fprintf(fp, "%d-%d-%d\n", year, month, date);
   sprintf(buf, "%d-%d-%d\n", year, month, date);
   
   return 0;
}

二、文件I/O

(一) 文件I/O - 介绍

什么是文件I/O

  • posix(可移植操作系统接口)定义的一组函数

  • 不提供缓冲机制, 每次读写操作都引起系统调用

  • 核心概念是文件描述符

  • 访问各种类型文件

  • Linux下, 标准IO基于文件IO实现

(二) 文件I/O - open

1. 文件I/O - open

open函数用来创建或打开一个文件:

#include <fcntl.h> int open(const char *path, int oflag, …);

  • 成功时返回文件描述符; 出错时返回EOF

  • 打开文件时使用两个参数

  • 创建文件时第三个参数指定新文件的权限

  • 只能打开设备文件

 

2. 文件I/O - open - 示例1

以只写方式打开文件1.txt。 如果文件不存在则创建, 如果文件存在则清空:

int fd;
if ((fd = open(“1.txt”, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
	perror(“open”);
    return -1;
}…
…  
3. 文件I/O – open – 示例2

以读写方式打开文件1.txt。 如果文件不存在则创建, 如果文件存在则报错:

int fd;
if ((fd = open(“1.txt”, O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) {
if (errno == EEXIST) {
	perror(“exist error”);
} else {
	perror(“other error”);
}
}  

(三) 文件I/O – close

1. 文件I/O - close

close函数用来关闭一个打开的文件:

#include <unistd.h> int close(int fd);

  • 成功时返回0; 出错时返回EOF

  • 程序结束时自动关闭所有打开的文件

  • 文件关闭后, 文件描述符不再代表文件

2. 文件I/O - 示例
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

int main()
{
   int  fd;
   if ((fd  = open("1.txt", O_RDWR|O_CREAT|O_EXCL, 0666)) < 0) {
      if (errno == EEXIST) { 
         perror("exist error");
      } else {
         perror("other error");
      }
   }
   
   return 0;
}

(四) 文件I/O - read

1. 文件I/O - read

read函数用来从文件中读取数据:

#include <unistd.h> ssize_t read(int fd, vo id *buf, size_t count);

  • 成功时返回实际读取的字节数; 出错时返回EOF

  • 读到文件末尾时返回0

  • buf是接收数据的缓冲区

  • count不应超过buf大小

2. 文件I/O -read - 示例

从指定的文件(文本文件)中读取内容并统计大小

int main(int argc, char *argv[]) {
{
int fd, n, total = 0;
char buf[64];
if (argc < 2) {
	printf(“Usage : %s <file>\n”, argv[0]); return -1;
}if((fd = open(argv[1], O_RDONLY)) < 0) {
	perror(“open”); return -1;
}
    while ((n = read(fd, buf, 64)) > 0) {
total += n;
}…
…

(五) 文件I/O - write

1. 文件I/O -write

write函数用来向文件写入数据:

#include <unistd.h> ssize_t write(int fd, void *buf, size_t count);

  • 成功时返回实际写入的字节数; 出错时返回EOF

  • buf是发送数据的缓冲区

  • count不应超过buf大小

2. 文件I/O – write – 示例

将键盘输入的内容写入文件, 直到输入quit

int fd;
char buf[20];
if ((fd = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
perror(“open”); return -1;
}
while (fgets(buf, 20, stdin) != NULL) 
if (strcmp(buf, “quit\n”) == 0) break;
write(fd, buf, strlen(buf));
}

(六) 文件I/O - lseek

1. 文件I/O - lseek

lseek函数用来定位文件:

#include <unistd.h> off_t lseek(int fd, off_t offset, intt whence);

  • 成功时返回当前的文件读写位置; 出错时返回EOF

  • 参数offset和参数whence同fseek完全一样

2. 示例1(复制文件)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

#define  N  64

int main(int argc, char *argv[])
{
   int fds, fdt, n;
   char buf[N];
   
   if (argc < 3)
   {
      printf("Usage : %s <src_file> <dst_file>\n", argv[0]);
	  return -1;
   }
   
   if ((fds = open(argv[1], O_RDONLY)) == -1)
   {
      fprintf(stderr, "open %s : %s\n", argv[1], strerror(errno));
	  return -1;
   }
   
   if ((fdt = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0666)) == -1)
   {
      fprintf(stderr, "open %s : %s\n", argv[2], strerror(errno));
	  return -1;
   }
   
   while ((n = read(fds, buf, N)) > 0)
   {
      write(fdt, buf, n);
   }
   close(fds);
   close(fdt);
   
   return 0;
}
3. 示例2(键盘输入内容到文件)
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
    int  fd;
    char  buf[20];

    if ((fd  = open(argv[1], O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) {
           perror("open");  
		   return -1;
    }
    while (fgets(buf, 20, stdin) != NULL) {
        if (strcmp(buf, "quit\n") == 0) break;
        write(fd, buf, strlen(buf));
    }
	close(fd);
	
	return 0;
}
4. 示例3(统计文件大小)
#include <stdio.h>
#include <fcntl.h>

int  main(int argc, char *argv[])
{
    int  fd, n, total = 0;
    char  buf[64];
    if (argc < 2) {
       printf("Usage : %s <file>\n", argv[0]);  return -1;
    }
    if ((fd  = open(argv[1], O_RDONLY)) < 0) {
           perror("open");  return -1;
    }
    while ((n = read(fd, buf, 64)) > 0) {
        total += n;
    }

	printf("the length of %s is %d\n", argv[1], total);
	close(fd);
	
	return 0;
}

(七)访问目录

1. 访问目录 – opendir

opendir函数用来打开一个目录文件:

#include <dirent.h> DIR *opendir(const char *n ame);

  • DIR是用来描述一个打开的目录文件的结构体类型

  • 成功时返回目录流指针; 出错时返回NULL

2. 访问目录 – readdir

readdir函数用来读取目录流中的内容:

#include <dirent.h> struct dirent *readdir(DIR *dirp);

  • struct dirent是用来描述目录流中一个目录项的结构体类型

  • 包含成员char d_name[256] 参考帮助文档

  • 成功时返回目录流dirp中下一个目录项;

  • 出错或到末尾时时返回NULL

3. 访问目录 – closedir

closedir函数用来关闭一个目录文件:

#include <dirent.h> int closedir(DIR *dirp);

  • 成功时返回0; 出错时返回EOF

4. 访问目录 – 示例代码

打印指定的目录下所有文件名称

int main(int argc, char *argv[]) {
{
DIR *dirp;
struct dirent *dp;
if (argc < 2) {
	printf(“Usage : %s <directory>\n”, argv[0]); return -1;
}if((dirp = opendir(argv[1])) == NULL) {
	perror(“opendir”); return -1;
}
    while ((dp = readdir(dirp)) != NULL) {
printf(“%s\n”, dp->d_name);
}c
losedir(dirp);

(八) 修改文件访问权限 – chmod/fchmod

chmod/fchmod函数用来修改文件的访问权限:

#include <sys/stat.h> int chmod(const char *path, mode_t mode); int fchmod(int fd, mode_t mode);

  • 成功时返回0; 出错时返回EOF

  • root和文件所有者能修改文件的访问权限

示例: chmod(“test.txt”, 0666);

(九) 文件属性

1. 获取文件属性 – stat/lstat/fstat

stat/lstat/fstat函数用来获取文件属性:

#include <sys/stat.h> int stat(const char *path, struct stat *buf); int lstat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf);

  • 成功时返回0; 出错时返回EOF

  • 如果path是符号链接stat获取的是目标文件的属性; 而lstat获取的是链接文件的属性

2.文件属性 – struct stat

struct stat是存放文件属性的结构体类型:

  • mode_t st_mode; 类型和访问权限

  • uid_t st_uid; 所有者id

  • uid_t st_gid; 用户组id

  • off_t st_size; 文件大小

  • time_t st_mtime; 最后修改时间

(十) 文件的类型和访问权限

1. 文件类型 – st_mode

通过系统提供的宏来判断文件类型:

st_mode & 0170000

  • S_ISREG(st_mode) 0100000

  • S_ISDIR(st_mode) 0040000

  • S_ISCHR(st_mode) 0020000

  • S_ISBLK(st_mode) 0060000

  • S_ISFIFO(st_mode) 0010000

  • S_ISLNK(st_mode) 0120000

  • S_ISSOCK(st_mode) 0140000

2. 文件访问权限 – st_mode

通过系统提供的宏来获取文件访问权限:

S_IRUSR00400bit:8
S_IWUSR002007
S_IXUSR001006
S_IRGRP000405
S_IWGRP000204
S_IXGRP000103
S_IROTH000042
S_IWOTH000021
S_IXOTH000010
3. 示例1(显示目录内容)
#include <stdio.h>
#include <dirent.h>

int  main(int argc, char *argv[])
{
    DIR *dirp;
    struct dirent *dp;
	
    if (argc < 2) {
       printf("Usage : %s <directory>\n", argv[0]);  
	   return -1;
    }
    if ((dirp  = opendir(argv[1])) == NULL) {
           perror("opendir");  
		   return -1;
    }
    while ((dp = readdir(dirp)) != NULL) {
        printf("%s\n", dp->d_name);
    }
    closedir(dirp);

	return 0;
}
4. 示例2(显示文件属性)
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
   struct stat buf;
   int n;
   struct tm *tp;
   
   if (argc < 2)
   {
      printf("Usage : %s <file>\n", argv[0]);
	  return -1;
   }
   
   if (lstat(argv[1], &buf) < 0)
   {
      perror("lstat");
	  return -1;
   }
   
   switch (buf.st_mode & S_IFMT)
   {
   case S_IFREG:
      printf("-");
	  break;
   case S_IFDIR:
      printf("d");
	  break;
   }
   
   for (n=8; n>=0; n--)
   {
      if (buf.st_mode & (1<<n))
	  {
	     switch (n % 3)
		 {
		    case 2:
			   printf("r");
			   break;
			case 1:
			   printf("w");
			   break;
			case 0:
			   printf("x");
			   break;
		 }
	  }
	  else
	  {
	     printf("-");
	  }
   }
   
   printf(" %lu", buf.st_size);
   tp = localtime(&buf.st_mtime);
   printf(" %d-%02d-%02d", tp->tm_year+1900, tp->tm_mon+1, tp->tm_mday);
   printf(" %s\n", argv[1]);
   
   return 0;
}

三、静态库与动态库

(一) 库的概念

库是一个二进制文件, 包含的代码可被程序调用

  • 标准C库、 数学库、 线程库……

  • 库有源码, 可下载后编译; 也可以直接安装二进制包

  • /lib /usr/lib

(二) 库的知识
  • 库是事先编译好的, 可以复用的代码。

  • 在OS上运行的程序基本上都要使用库。 使用库可以提高开发效率。

  • Windows和Linux下库文件的格式不兼容

  • Linux下包含静态库和共享库

(三) 静态库特点
1. 静态库的特点

_ 编译(链接)时把静态库中相关代码复制到可执行文件中_

  • 程序中已包含代码, 运行时不再需要静态库

  • 程序运行时无需加载库, 运行速度更快

  • 占用更多磁盘和内存空间

  • 静态库升级后, 程序需要重新编译链接

2. 静态库创建(1)
  • 确定库中函数的功能、 接口_

  • 编写库源码hello.c_

#include <stdio.h>
void hello(void) {
	printf(“hello world\n”);
	return;
}
  • 编译生成目标文件 $ gcc -c hello.c -Wall

3. 静态库创建(2)
  • 创建静态库 hello $ ar crs libhello.a hello.o

  • 查看库中符号信息 $nm libhello.a hello.o: 0000000 T hello U puts

4. 链接静态库
  • 编写应用程序test.c

    #include <stdio.h>
    void hello(void);
    int main() {
    	hello();
    	return 0;
    }
  • 编译test.c 并链接静态库libhello.a

    $ gcc -o test test.c -L. -lhello
    $ ./test
    hello world  
(四) 共享库
1. 共享库特点

编译(链接)时仅记录用到哪个共享库中的哪个符号, 不复制共享库中相关代码

  • 程序不包含库中代码, 尺寸小

  • 多个程序可共享同一个库

  • 程序运行时需要加载库

  • 库升级方便, 无需重新编译程序

  • 使用更加广泛

2. 共享库创建(1)
  • 确定库中函数的功能、 接口

  • 编写库源码hello.c bye.c

#include <stdio.h>
void hello(void) {
	printf(“hello world\n”);
	return;
}
  • 编译生成目标文件

    $ gcc -c -fPIC hello.c bye.c -Wall

3. 共享库创建(2)
  • 创建共享库 common

    $ gcc -shared -o libcommon.so.1 hello.o bye.o

  • 为共享库文件创建链接文件

  • $ ln -s libcommon.so.1 libcommon.so

  • 符号链接文件命名规则

  • lib<库名>.so

4. 链接共享库
  • 编写应用程序test.c

    #include <stdio.h>
    #include “common.h”
    int main() {
    	hello();
    	bye();
    	return 0;
    }
  • 编译test.c 并链接共享库libcommon.so $ gcc -o test test.c -L. -lcommon

5. 加载共享库
  • 执行程序

    $ ./test

    ./test: error while loading shared libraries: libcommon.so

    cannot open shared object file : No such file or directory

  • 添加共享库的加载路径

$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:

$ ./test

hello world

`bye! `
6. 如何找到共享库
  • 为了让系统能找到要加载的共享库, 有三种方法 :

  • 把库拷贝到/usr/lib和/lib目录下

  • 在LD_LIBRARY_PATH环境变量中添加库所在路径

  • 添加/etc/ld.so.conf.d/*.conf文件, 执行ldconfig刷新

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值