linux中的基础IO


1. 回顾库函数IO接口:

fopen,fwrite,fread,fseek,fclose

(1) FILE *fopen(char *pathname, char *mode);

pathname:文件路径名; mode:文件的打开方式
返回值:打开文件成功则返回一个FILE*文件流指针作为文件的操作句柄,失败返回NULL

r   只读方式打开文件,文件必须存在
r+  读写方式打开文件,文件必须存在
w   只写方式打开文件,文件不存在则自动创建,存在则清空内容
w+  读写方式打开文件,文件不存在则自动创建,存在则清空内容
a   追加写方式打开文件,文件不存在则自动创建,存在则写入数
    据总是写入文件末尾
a+  读+追加写方式打开文件,文件不存在则自动创建,存在则写
    入数据总是写入文件末尾
b   二进制方式打开文件

在系统调用IO接口中并不区分文本和二进制数据,统一按照二进制
进行处理,但是库函数不一样,区分文本和二进制,默认文本操作,但
是有的文本一个字符占据多个字节.

例如:一个文件中有10个字节数据,但是读取的时候读取完毕后读取
大小是9,这个9表示的不是9个字节而是9个字符。

(2) size_t fwrite(const viod* data,size_t bsize,size_t nmem,FILIE *fp)

data:要写入文件的数据的所在空间首地址;

bsize: 块大小;   nmem:块个数;  bsize*nmem 要写入文件大小

fp:fopen返回的文件流指针,标识要操作哪一个文件.

返回值:返回实际完整操作的块个数.

注意
r+/w/w+方式文件打开之后,文件的默认读写位置在文件起始,
如果文件中本身就有数据,就会覆盖,并且读写位置会随着数据的写入向后偏移;
a/a+方式打开文件之后,读默认是从文件起始读的,
但是如果写会使读写位置跳转到文件末尾,将数据追加到末尾

(3) size_t fread(void *buf, size_t bsize, size_t nmem, FILIE *fp)

buf:一块空间的首地址,用于存放从文件中读取的数据

bsize:块大小;  nmem: 块个数;  bsize*nmem 要写入文件大小

fp:文件流指针

返回值:成功返回实际操作的完整块个数,读取到文件末尾返回0;
      出错了也返回0.

注意:
这个函数返回值存在多义性,返回0的时候无法直接确定是出错还是读到文件末尾了。
例如一个文件100字节,现在读取数据块大小200,块个数1,因为没有读取完整的一块,并且读取到了文件末尾,因此返回了一个0,这时候这个0无法确定出错了还是正常的。
因此,建议fread读取数据的时候,块大小设置为1,想要读取的长度
设置为块个数
,这样返回值就能告诉我们读取了多少数据。

(4) int fseek(FILE *fp, int offset, int whence);

改变文件读写位置,跳转到哪里就从哪里开始读写

fp:文件流指针;  offset:偏移量;   whence:偏移起始位置

SEEK_SET-起始 / SEEK_CUR-当前 / SEEK_END-末尾

返回值:成功返回0;失败返回-1

(5) int fclose(FILE *fp);

关闭文件释放资源

2. 系统调用IO接口:

open,read,write,lseek,close

(1) int open(char *pathname, int flag, mode_t mode);

pathname:要打开的文件路径名
flag:文件打开标志—决定了文件的打开方式

必选标志: O_RDONLY / O_WRONLY / O_RDWR 只能选一个
可选标志: 
O_APPEND-追加写 | O_CREAT-文件不存在则创创建 | O_TRUNC-文件存在则截断为0 | 0_EXCL-文件存在则报错	
例如:
w+:读写+创建+截断 O_RDWR|O_CREAT|O_TRUNC 
a+:读+追加写,创建 O_RDWR|O_APPEND|O_CREAT

mode:文件的权限,通常采用八进制数字设定 0777,如果使
     用了O_CREAT就一定要有第三个参数
     
返回值:成功返回一个非负整数-文件描述符-是文件的操作句柄;
      失败返回-1;

文件的创建权限是受到umask掩码影响的.
实际文件得到的权限 mode&(~umask)

mode_t umask(mode_t mask);
通常在程序起始阶段调用umask(0)将当前进程的创建掩码设置为0

(2) ssize_t read(int fd, void *buf, int len);

fd:open返回的文件描述符-操作句柄-表示操作哪个文件

buf:一块空间地址,用于存放读取到的数据

len:要读取的数据长度

返回值:成功返回实际读取到的数据长度;  失败返回-1;

(3) ssize_t write(int fd, void *data, int len);

fd:文件描述符-操作句柄;

data:要写入文件的数据的空间首地址;

len:想要写入文件的数据长度

返回值:成功返回实际写入文件的数据长度;  失败返回-1;

(4) off_t lseek(int fd, off_t offset, int whence);

fd:文件描述符;

offset:偏移量;

whence:偏移起始位置 SEEK_SET / SEEK_CUR / SEEK_END
返回值:成功返回跳转后的位置相对于文件起始位置的长度
      (额外用法:跳转到文件末尾则返回文件长度);
      失败返回-1

(5) int close(int fd);

关闭文件释放资源

3. 文件描述符:open返回的一个非负整数

文件描述符本质上就是内核中,进程打开的文件IO信息指针数组的一个下标
文件描述符
当我们通过描述符操作文件的时候,
在pcb中找到files_struct结构体指针,
进而找到结构,找到结构体中的数组,
以描述符作为下标获取到文件描述信息的地址,
通过描述信息操作文件。

打开文件:描述文件,然后将描述信息结构的地址添加到数组中,
        返回对应下标作为文件描述符。
操作文件:通过描述在pcb的fd_arry数组中找到文件描述信息,
        进而操作文件。

4. 重定向

改变数据的流向,让原本写入A文件的数据,不再写入A文件而是写入指定的B文件中

一个进程运行起来之后,默认会打开三个文件:

标准输入   标准输出    标准错误
stdin     stdout     stderr      文件流指针
  0         1           2        文件描述符 

在这里插入图片描述

(1)标准输出重定向原理

1描述符默认是显示器文件,但是一旦重定向,就会让1描述符不再指向显示器,而是指向指定的文件的描述信息,这时候,原本写入到1中的数据,就没有在写入显示器了,而是写入到指定的文件中。

所有代码都没有改版,只是改变了文件描述信息数组中下标对应的描述信息指针(描述符没变,改变了操作的文件)

(2)ls > >test.txt实现原理

ls是一个指令程序,运行时会启动进程,进程由shell创建,
shell创建了ls子进程之后只需要将这个进程的1号描述符对应的文件信息指针改变,
就可以实现不改变ls程序的情况下改变数据的流向。

注意:
程序替换,并不会初始化文件描述信息指针数组,因此提前修改了1号描述符的信息之后,程序替换,向1中写入数据就会把数据写入指定的文件。

(3)接口

int dup2(int oldfd, int newfd);

让newfd复制oldfd对应的描述信息,
成功则关闭newfd原来指向。

说白了dup2就是让两个描述符都指向oldfd所指向的文件,相当于是对newfd进行了重定向!

(4)在minishell中添加重定向功能的实现

>>:追加重定向
>:清空重定向

重定向实现: 在创建了子进程,程序替换之前,将标准输出给重定向到指定的文件上dup2(fd,1)
追加:打开fd的文件时使用O_APPEND
清空:打开fd文件时使用O_TRUNC

ls -l > > test.txt

  1. 捕捉输入
  2. 解析是否包含重定向(将命令截断得到 命令+重定向类型+重定向文件名)
  3. 解析指令(得到 命令名称,运行参数)
  4. 创建子进程
    (1)子进程中先按照指定重定向类型打开重定向文件
    (2)对子进程进行标准输出重定向,让标准输出1指向打开的文件
    (3)程序替换
  5. 等待子进程退出

5. 文件描述符与文件流指针:

(1)文件描述符:struct FILE *

(2)文件流指针:int

注意:
类型不同,不能混用。

文件流指针是库函数的操作句柄,文件描述符是系统调用接口的操作句柄;

库函数和系统调用接口是一层上下级调用的关系:库函数中封装的就是系统调用接口。

dup2(fd, 1);      重定向1号描述符-标准输出,影响文件流指针

fwrite("hello bit\n",10, 1,stdout);

fwrite(用的是文件流指针)  ->  write(文件描述符)

stdout ->_fileno=fd; !!!文件流指针结构体中封装了文件描述符!!!

fwrite("hello world\n, 12, 1, stdout");

文件流指针中不仅有文件描述符,还有缓存区;
也就是说缓冲区不是系统调用中定义的,而是在上层封装的时候在流指针中封装的;
这个缓冲区所占空间-用户空间- - 称之为用户态缓冲区。
这也是为什么_exit系统调用接口退出进程前不刷新缓冲区的原因。

6. 动态库与静态库的生成与使用

(1) 生成

库的命名lib作为前缀,中间名称,最后.so作为动态库后缀, .a作为静态库后缀

(2) 库

已写好的功能性代码,打包而成的一个文件(不能有mian)

  1. 先将所有的.c编译汇编完成生成自己的.o二进制指令

    gcc -fPIC -c $^ -o $@    -fPIC 产生位置无关代码
    
  2. 将所有的.o二进制指令文件打包到一起(生成的不是可执行程序,而是库)

    gcc –shared $^ -o lib**.so   -shared生成动态库
    
    ar -cr lib**.a **.o **.o      -cr生成静态库
    

(3) 使用:

1.链接生成可执行程序时使用:

(1) 使用-l来指定要链接的库名称(前提:库文件放到指定路径 下: /user/lib64)

export LIBRARY_PATH=${LIBRARY_PATH}:./  
设置环境变量,将库文件所在目录加入到环境变量路径中。

(2) 使用gcc -L选项指定库文件的所在路径(常用于链接静态库-因为静态库没有依赖,运行时不用加载)

gcc main.c -o main -L./ -lchild

2.运行可执行程序时使用:
(仅限于动态库—因为只有链接动态库 运行程序的时候才需要加载动态库)

静态库使用时是把库中用到的代码直接放入到可执行程序,
动态库使用只记录函数符号信息,因此运行时才依赖。

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:./ 
设置环境变量中的库文件加载路径

总结:
1.将库文件放到指定路径 /usr/lib64
2.设置环境变量:LIBRARY_PATH + LD_LIBRARY_PATH
3.使用gcc -L选项
一个目录下,既有动态库,也有静态库,同名,gcc生成可执行程序时默认优先链接动态库
因为gcc的默认链接方式就是动态链接 – 链接动态库

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值