linux高级篇——io系统编程 -凯发k8官方网
1.文件io
2.标准io
3.动静态库的制作
4.目录io
文件io简介
涉及哪些接口?
input ,output
是从用户空间角度考虑的输入与输出:
从内核读取数据或从文件中读取数据,叫:input read函数
写数据到内核或写数据到文件中,叫:output write函数
内核中有很多文件,应该写到哪一个文件中呢,或从哪一个文件中读呢?
(内核应该负责管理这些文件-文件管理)
因此在写入或读出之前用户应该指定某个文件,即要创建或打开某个文件
即:read ,write 这二个函数应该有一个参数是指定某个文件
read或write 函数之前有一个函数: open
操作之后,还要关闭这些文件 close
open – 打开或创建一个文件
open(char *, flag, mode)在fcntl.h文件中声明。函数的作用:创建或打开某个文件,参数:最多有三个参数;
第一个参数,char * 包含有文件名和路径
第二个参数:flag 打开文件的方式
第三个参数:mode 创建文件的权限。
flag内容如下:
flag 功能
o_rdonly 只读
o_wronly 只写
o_rdwr 读写
o_creat 创建一个文件
o_excl 如果使用o_creat时文件存在,则可返回错误消息(返回-1)。如果没使用o_creat,那么不管打开的文件存不存在,都会创建一个新的文件,这一 参数可测试文件是否存在。
o_trunc 打开文件(会把已经存在的内容给删除)。
o_append 追加方式打开文件(不会把已经存在的内容给删除)。
返回值:
成功:文件描述符,它是一个非负的正整数,即文件的id号,相当于人的身份证号;
出错:-1。
open 函数创建文件时的权限是:
mode & (~umask)
b111 111 111 & ~(b 000 010 010) = b 111 101 101
umask是掩码,我们可以通过
umask 掩码值(4位八进制)来修改
什么是文件描述符?
内核的一个重要功能是文件管理,系统有非常多的文件,内核怎样认识每一个文件呢?内核采用id号的方式标识这些文件,inode 号,node号表示不同的文件,比如ls –lai i,在一个进程下只要文件不一样,inode号就不一样。
那么这些内核的文件的id号,在每个用户的程序中怎样映射的呢?即是文件描述符。
open函数的返回值就是这个id号
id号有什么规律呢?
从0开始累加,
程序进行时(进程),内核会自动打开3个文件描述符,0,1,2,分别对应,标准输入、输出和出错,这样在程序中,每打开一个文件,文件描述符值从3开始累加。
write(int fd, void *buf, size_t count ):
第一个参数:向哪一个文件中去写;第二个参数:向这个文件中写什么内容;第三个参数:向这个文件中写多少个。函数的返回值:是实际写的字节数。
返回值:是实际写的字节数
read(int fd, void *buf, size_t count)
第一个参数:从哪一个文件中去读;第二个参数:读到什么地方去;第三个参数:读多少个。函数的返回值:是实际读的字节数。
返回值:是实际读的字节数
上面这个程序是无法把程序读出来的,因为我们在对文件进行写操作的时候,文件指针已经指向最后,读的时候会接着文件指针读,所以就读不出来东西
leek(int fd, off_t offset, int whence),
该函数的头文件:sys/types.h unistd.h;
功能:调整读写的位置指针;
第一个参数:要调整的文件的文件描述符;
第二个参数:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移);
第三个参数:当前位置的基点,有三个标志,
seek_set:当前位置为文件的开头,新位置为偏移量的大小;
seek_cur:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
seek_end:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。函
数的
返回值:成功:文件当前的位置,出错:-1。
5.close(fd),
调用close()函数可以关闭一个打开的文件。
调用成功返回0,出错返回-1,并设置errno;
注:当一个进程终止时,该进程打开的所有文件都由内核自动关闭;
上面程序就可以读到hello linux
如果lseek这么使用的话那么会读到:llo linux
例子:拷贝文件==》cp命令的实现
2.1与文件io的区别?
文件io:是直接调用内核提供的系统调用函数, 头文件是unistd.h
标准io:是间接调用系统调用函数,头文件是: stdio.h
printf是间接调用,write是直接调用,1这个文件描述符就代码着标准输出
之前学过:输入输入相关的函数,都是和标准的输入(键盘),标准的输出(显示器)
getchar(),putchar() ----一个字符
gets(buf),puts(buf) ----一串字符
scanf(),printf() ---- 一个字符,一串字符都可以
与一些普通文件的读写没有关系,也即这些函数不能读写普通文件。
标准io中的相关函数,不仅可以读写普通文件,也可以向标准的输入或标准的输出中读或写。
三个缓存的概念(数组):
1.我们的程序中的缓存,就是你想从内核读写的缓存(数组)----用户空间的缓存
2.每打开一个文件,内核在内核空间中也会开辟一块缓存,这个叫内核空间的缓存
文件io中的写即是将用户空间中的缓存写到内核空间的缓存中。
文件io中的读即是将内核空间的缓存写到用户空间中的缓存中。
3.标准io的库函数中也有一个缓存,这个缓存称为----库缓存
例1:测试验证库缓存的存在
标准io库函数在什么时候会调用系统调用函数?
标准io库缓存写满或满足一定条件时,会调用系统调用函数。
printf满足一定条件 : 遇到\n 时,即会将库缓存的内容写到内核中,即调用了系统调用函数。( printf对应行缓存1024个字节)
#include "stdio.h" int main() {char buf[]="hello linux\n";printf("%s",buf);while(1);return 0; }库缓存写满时,会调用系统调用函数,将lib_buffer 内容写到kernel_buffer中去
#include "stdio.h" int main() {char buf[]="hello linux";int i=0;while(i< 93){printf("%s",buf);i;}printf("h");while(1);return 0; }文件io: 标准io
open fopen
close fclose
lseek fseek, rewind
read 读写函数比较多(分三类,全缓存、行缓存和无缓存)
write
file *fopen (const char *path, const char *mode);
返回值:file * 文件流指针 类似于文件io 中的文件描述符
file 定义:struct _io_file,在/usr/include/libio.h
包含读写缓存的首地址、大小、位置指针等。
标准的输入流:stdin 0
标准的输出流:stdout 1
标准的出错流:stderr 2
文件io flag
r或rb 打开只读文件,该文件必须存在。
r 或r b 打开可读写的文件,该文件必须存在。
w或wb 打开只写文件,若文件存在则文件长度清为0,即会擦些文件以
前内容。若文件不存在则建立该文件。
w 或w b或wb 打开可读写文件,若文件存在则文件长度清为零,即会擦些文件
以前内容。若文件不存在则建立该文件。
a或ab 以附加的方式打开只写文件。若文件不存在,则会建立该文件,
如果文件存在,写入的数据会被加到文件尾,即文件原先的内容
会被保留。
a 或a b或ab 以附加方式打开可读写的文件。若文件不存在,则会建立该文
件,如果文件存在,写入的数据会被加到文件尾后,即文件原先
的内容会被保留。
b:二进制文件
r: 只读方式打开文件,文件必须存在;
w或a:只写方式打开文件,文件不存在则创建;
区别: w等价o_trunc,a等价o_append;
:读写方式打开文件,文件必须存在;
例:以读写方式打开一个文件,该文件必须存在: r
以追加方式打开一个文件,若文件不存在,则创建: a或a
fopen最终生成文件的权限都是0666&(~umask)
int fclose(file *stream)
fclose()调用成功返回0,失败返回eof,并设置errno
在该文件被关闭之前,刷新缓存中的数据。如果标准i / o库已经为该流自动分配了一个缓存,则释放此缓存。
读写函数:
三类读写函数:
一类**:行缓存** 遇到新行符(\n) 或写满缓存时,即调用系统调用函数
读:fgets, gets, printf, fprintf,sprintf
写:fputs, puts,scanf
一个字符的读写,是否是行缓存?
读:fgetc, getc, getchar
写:fputc, putc,putchar
二类:无缓存 只要用户调这个函数,就会将其内容写到内核中
三类:全缓存 只有写满缓存再调用系统调用函数
读:fread
写:fwrite
行缓存的读写函数fgets和fputs
char *fgets (char *s, int size, file *stream)
第一个参数:缓存,即读到哪里去
第二个参数:读多少个字节
第三个参数:从什么地方读
返回值若成功则为s(缓存的地址),若已处文件尾端或出错则为null
int fputs(const char *s,file *stream);
第一个参数:缓存,即写什么内容
第二个参数:写到哪里去
若成功则为非负值,若出错则为eof -1 。
同样的下面这个文件是不能读出数据的(需要调整文件位置指针)
刷新缓存函数:fflush(file *fp)
把库函数中的缓存的内容强制写到内核中。
fclose()函数里边包含fflush();
无缓存,行缓存,全缓存
stderr 无缓存
stdout 行缓存
下面程序是写入标准输出,由于标准输出是行缓存,所以下面程序没有结束时不会写入到内核当中的
但是下面程序在执行的时候是可以把数据写进文件的,所以可以看出stderr是无缓存的
调整读写位置指针函数:
fseek() 参数与lseek是一样的但是返回值不一样
lseek的返回值是:当前文件的位置指针值;
fseek()的返回值是:成功返回0,失败返回-11;
rewind(file *fp) 用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
rewind()等价于(void)fseek(fp 0, seek_set);
ftell(file *fp)
用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1l;
行缓存的读写函数gets和puts
char *gets(char *s);
int puts(const char *s);
gets 与fgets的区别:
gets()时不能指定缓存的长度,这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果;
gets()只能从标准输入中读,fgets()还可以从普通文件里边读;
gets()与fgets()的另一个区别是: gets()并不将新行符存入缓存中, fgets 将新行符存入缓存中(新行符就是回车);
puts 与fputs的区别:
puts()只能向标准输出中写;
puts()与fputs()的另一个区别是: puts 输出时会添加一个新行符, fputs不会添加;
fprintf、printf、sprintf 行缓存的函数
int fprintf(file *stream,”字符串格式”) ;
fprintf可以输出到文件中,也可输出到显示器,
printf 只能输出到显示器中。
int sprintf(str *, “字符串格式”)
输出内容到一个字符串中
一个字符读写函数fgetc和fputc
int fgetc(file *fp)
功能:从文件中读取一个字符; 不仅仅可以从标准输入上读,还可以从普通文件上读
参数:文件流
返回值:正确为读取的字符,到文件结尾或出错时返回eof。
int fputc(int c, file *fp)
功能:写一个字符到文件中
参数:第一个参数为要写的字符,第二个参数为文件流
返回值:成功则返回输入的字符,出错返回eof。
fputc有缓存,但不是行缓存函数所以加上‘\n’也不会把缓冲数据刷新到内核中,所以下面例子是不能把数据写到a.c中去的。只有加上flussh就能把数据刷新到内核中
fgets的使用:
char *fgets (char *s, int size, file *stream)
返回值若成功则为s(缓存的地址),若已处文件结尾或读错则为null
int fgetc(file *fp)
返回值:正确为读取的字符,到文件结尾或读错时返回eof。-1
当返回错误时,怎样判读是已经到达文件结尾(fgets与fgetc),还是读错呢?
3.8int feof(file *stream);
功能:判断是否已经到文件结束
参数:文件流
返回值:到文件结束,返回为非0,没有则返回0
3.9 int ferror(file *stream);
功能:判断是否读写错误
参数:文件流
返回值:是读写错误,返回为非0,不是则返回0
3.10void clearerr(file *stream);
功能:清除流错误
参数:文件流
下面程序ferror始终为0(如果到文件结尾还读的话那么ferror的值为非0)所以不是读写错误,而是流错误
例 cat 命令的实现
全缓存的二个函数:
全缓存的函数是只要把库缓存写满才会把库缓存内容写进内核当中的。size和nmemb的区别,首先比如要写进int arr[100],那么size是4,nmemb是100
上面程序没有输出,是文件指针问题
fgetc与fputc与read,write等四组效率对比(拷贝同一个文件):
fread>fgets>fgetc>read
linux操作系统支持的函数库分为:
静态库,libxxx.a,在编译时就将库编译进可执行程序中。
优点:程序的运行环境中不需要外部的函数库。
缺点:可执行程序大(因为运行的时候已经导入函数)
动态库,又称共享库,libxxx.so,在运行时将库加载到可执行程序中。
优点:可执行程序小。
缺点:程序的运行环境中必须提供相应的库。
函数库目录:/lib /usr/lib。
静态库的制作:
1.生成目标文件:gcc -c file.c
2.静态函数库创建命令ar
ar -cr libfile.a file.o
-c: create的意思
-r: replace的意思,表示当插入的模块file.o已经存在libfile.a中,则覆盖。反之ar显示一个错误消息。
操作静态库的几个实例:
情况1: 如果从别处得到一个静态库libunknown.a,想知道其中包含哪些模块。
命令:ar -t libunknown.a
静态库的编译:gcc -o main main.c -l. -lfile编译main.c就会把静态函数库整合进main。
其中:
-l指定静态函数库的位置供查找,注意l后面还有’.’,表示静态函数库在本目录下查找。
-l则指定了静态函数库名,由于静态函数库的命名方式是lib***.a,其中的lib和.a忽略。
删除libaddsub.a后main依然可以运行,因为静态库的内容已经整合进去了。
动态函数库的制作:
1.生成目标文件:gcc -c file.c
2. gcc -shared -fpic -o libfile.so file.o
-fpic:声明产生位置无关代码。
-shared:生成共享库。
用上述命令生成libaddsub.so 动态函数库。
gcc -o out main.c -l. -lfile
此时还不能立即./out,因为在动态函数库使用时,会查找/usr/lib /lib目录下的动态函数库,而此时我们生成的库不在里边。
第一种方法:
libaddsub.so放到/usr/lib 或/lib中去。(mv)
第二种方法,假设libfile.so在/home/linux/file 环境变量方法
export ld_library_path=/home/linux/addsub:$ld_library_path
第三种方法:
在/etc/ld.so.conf文件里加入凯发k8官方网生成的库的目录,然后/sbin/ldconfig。
/etc/ld.so.conf是非常重要的一个目录,里面存放的是链接器和加载器搜索共享库时要检查的目录,默认是从/usr/lib /lib中读取的,所以想要顺利运行,可以把我们库的目录加入到这个文件中并执行/sbin/ldconfig。
#include
#include
目录i/o与文件i/o函数的比较
目录i/o 文件i/o
opendir 只能打开目录
mkdir 创建目录 open
readdir 读目录 read
rewinddir 调整位置指针
telldir
seekdir rewind
ftell
fseek
closedir:关闭目录 close
1.opendir
dir *opendir(const char *pathname) ;
参数:打开的目录以及路径
返回值:成功返回目录流指针,出错返回null。
int mkdir(const char * path, mode_t mode)
path为欲创建的目录文件路径,
mode为该目录的访问权限
返回值:若目录创建成功,则返回0;否则返回-1
生成的目录权限仍和umask有关系。
2.readdir
struct dirent *readdir(dir *dr);
参数:目录流指针
返回值:成功则为struct dirent指针,若在目录尾或出错则返回null。
struct dirent定义在头文件dirent.h中。
此结构至少包含下列两个成员:
struct dirent
{
ino_t d_ino; // inode 号
char d_name[name_max 1]; //文件名
}
按照上图只能读一个目录,因为目录里边是按照链表进行存储,使用一个死循环就可以把目录里的所有文件都输出出来
3.rewinddir:重置读取目录流的位置为开头
void rewinddir(dir *dr);
参数:目录流指针
long telldir(dir *dirp)
参数:目录流指针
返回值:目录流当前位置
void seekdir(dir *dirp , long loc)
类似于文件定位函数fseek(),在目录流上设置下一个readdir()操作的位置。
参数:目录流指针和偏移量
int close(dir *dr);
参数:目录流指针
返回值:成功返回 0,出错返回- 1。
总结
以上是凯发k8官方网为你收集整理的linux高级篇——io系统编程的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: linux命令行参数
- 下一篇: