前言:
1、IO编程:数据和文件之间相互通信的过程 。IO编程就是对文件的输入输出操作。
2、IO编程分为标准IO,和文件IO。
简单介绍一下两者区别:
标准IO:由ANSIC中定义的用于I/O操作的一系列函数,引用C语言库支持C库操作应用层,,是一种高级IO,主要的操作对象是普通文件。为减轻系统开销,标准I/O使用时候会在用户区创建缓冲区,读写时先操作缓冲区,在合适的时机再通过系统调用访问实际空间,从而减少使用系统的调用次数。
文件I/O:文件IO是由POSIX(可以移植系统接口)定义的,很多系统都支持POSIX标准,如UNIX,Linux。文件IO通常针对特殊文件,比如设备文件。但是文件IO不支持C库。
3、Linux中一切皆文件,其中文件可以分为一下七类:
普通文件 -
目录文件 d
管道文件 p
字符设备 c
块设备 b
链接文件 l
套接字 s
一、Linux标准I/O编程
1、标准IO核心----流
1.1首先说一下什么是流:
标准IO核心对象就是流。当用标准IO打开一个文件时,就会自动创建一个FIFE结构体描述该文件,我们将该结构体称为流。标准I/O主要针对“流”进行操作。
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
1.2、Linux开机自启动文件
在Linux里面有三个特殊文件,会在每一次运行程序的时候自动打开
文件指针
标准输入(文件) stdin 键盘
标准输出(文件) stdout 屏幕
标准错误输出(文件) stderr 屏幕
2、标准IO的缓冲
标准IO缓冲有三种类型:全缓冲,行缓冲,无缓冲。
全缓冲:缓冲区满了,或者使用fflush刷新
行换从:缓冲区满了,或者使用fflush刷新,或者遇到\n。
无缓冲:向缓冲区中写数组,每写一个字符都会立即被刷新。
3、流的操作
流的操作包括:打开,关闭,读写,定位等。
3.1文件打开:
#include <stdio.h>
//打开文件
FILE *fopen(const char *pathname, const char *mode);
参数:
pathname:文件路径
mode:打开方式
r 只读的方式打开文件,文件必须存在。
r+ 读写的方式打开文件,文件必须存在。
w 只写的方式打开文件,文件不存在则创建,存在则清空
w+ 读写的方式打开文件,文件不存在则创建,存在则清空
a 追加只写方式打开文件,文件不存在则创建,存在则追加写入
a+ 追加读写方式打开文件,文件不存在则创建,存在则追加写入
返回值:
成功返回文件指针,失败返回 NULL ,并且设置错误号。
3.2文件的读写
3.2.1格式化读写:
格式化输出:
#include <stdio.h>
int printf(const char *format, ...);
//向stream指向的文件中输出数据
int fprintf(FILE *stream, const char *format, ...);
//向内存中输出数据,因为没有指定输出数据的大小,容易造成内存泄漏,因此不安全
int sprintf(char *str, const char *format, ...);
//向内存中输出数据,指定了输出数据大小,因此类型安全
int snprintf(char *str, size_t size, const char *format, ...);
参数:
str:内存首地址
size:内存的大小
format:数据的格式
变参:要输入的数据
格式化输入:
#include <stdio.h>
//从标准输入读取数据
int scanf(const char *format, ...);
//从steam指定的文件中输入读取数据
int fscanf(FILE *stream, const char *format, ...);
//从指定的内存中输入数据
int sscanf(const char *str, const char *format, ...);
3.2.2非格式化输入输出
非格式化的输入输出主要依赖于库函数的调用:
1>按字符读写:
#include <stdio.h>
int fgetc(FILE *stream);//从steam指定的文件中读取一个字符,是一个函数
int getc(FILE *stream);//从steam指定的文件中读取一个字符,是一个宏
int getchar(void); //从标准输入读取一个字符
参数:
stream:文件指针
返回值:
成功一个字符(int),出错或者到达文件末尾EOF。
#include <stdio.h>
int fputc(int c, FILE *stream); //向stream指向的文件中输入一个字符,是一个函数
int putc(int c, FILE *stream); //向stream指向的文件中输入一个字符,是一个宏
int putchar(int c); //向标准输出输出一个字符
参数:
c:要输入的字符
stream:文件指针
2>按行读写:
#include <stdio.h>
//向stream指向文件中读取一行数据,保存到s指向的内存中
//会将\n 一起读入到内存中
//最多只能读size -1 个字符,在末尾主动添加 \0
char *fgets(char *s, int size, FILE *stream);
参数:
s:内存首地址
size:读取字符的个数
stream:文件指针
返回值:
成功返回内存首地址,失败或者读到文件末尾返回NULL
// 默认向键盘获得一行数据,保存到s指向的内存中,因为没有限制内存长度,
// 容易造成内存泄漏
char *gets(char *s);
#include <stdio.h>
//向stream指向文件中写入一行数据
int fputs(const char *s, FILE *stream);
参数:
s:内存首地址
stream:文件指针
//默认向标准输出打印一行数据,保存到s指向的内存中,因为没有限制内存长度
// 容易造成内存泄漏
int puts(const char *s);
3>按快读写:
#include <stdio.h>
//按块读取
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数:
ptr:内存首地址
size:读取数据对象的大小
nmemeb:读取的个数
stream:文件指针
返回值:
实际读取到的个数,失败返回0
//按块写入
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
参数:
ptr:内存首地址
size:写入数据对象的大小
nmemeb:写入的个数
stream:文件指针
返回值:
实际写入到的个数,失败返回0
3.3文件流的定位
文件指针的偏移和定位
文件指针的偏移
#include <stdio.h>
//设置文件指针的偏移
int fseek(FILE *stream, long offset, int whence);
参数:
stream:文件指针
offset:相对于whence的偏移量
whence:偏移的起始位置
SEEK_SET, 文件的起始位置
SEEK_CUR, 文件指针当前的位置
SEEK_END, 文件的末尾
返回值:成功返回0,失败返回-1 ;
文件指针的定位
//定位文件指针的当前位置
long ftell(FILE *stream);
参数:
stream:文件指针
返回值:成功返回文件指针当前位置,失败 -1 ;
3.4关闭文件:
#include <stdio.h>
//关闭打开的文件
int fclose(FILE *stream);
3.5时间获取
获取标准时间:
#include <time.h>
time_t time(time_t *tloc);
参数:
tloc:存放标准时间的地址
返回值:成功返回标准时间,失败返回-1 ,并且设置错误号。
获得字符串时间
char *ctime(const time_t *timep);
参数:
timep:标准时间
返回值:成功返回字符串时间指针,失败NULL
获得当地的时间
struct tm *localtime(const time_t *timep);
参数:
timep:标准时间
返回值:成功结构体指针,失败返回NULL。
struct tm {
int tm_sec; /* Seconds (0-60) 秒数*/
int tm_min; /* Minutes (0-59) 分钟*/
int tm_hour; /* Hours (0-23) 小时*/
int tm_mday; /* Day of the month (1-31)日期 */
int tm_mon; /* Month (0-11) 实际月份 =tm_mon+1 */
int tm_year; /* Year - 1900 实际年份=tm_year+1900 */
int tm_wday; /* Day of the week (0-6, Sunday = 0)一周中的第几天 */
int tm_yday; /* Day in the year (0-365, 1 Jan = 0)一年中的第几天 */
int tm_isdst; /* Daylight saving time 冬令时间或者夏令时间*/
};
将当地时间转换成字符串时间。
char *asctime(const struct tm *tm);
参数:
tm:当地时间结构体地址
返回值:成功返回字符串时间指针,失败NULL
二、Linux文件I/O编程
1、文件描述符
1.1文件描述符概述:
Linux操作系统是基于文件的概念,文件是以字符序列构成的信息载体,所以我们可以将I/O设备当作文件来处理。因此与磁盘上普通文件进行交互所用的同一系统调用可以直接使用I/O设备。内核区分和引用特定的文件便需要使用到文件描述符。
文件描述符是有以特点:
1- 文件描述符是一个非负整数
2- 对于内核而言所有打开的文件都由文件描述符引用
3- 当打开或者创建文件的时候,内核会向进程返回一个最小的没有被占用的整数。作为文件描述符。
4- 当读写文件的时候,需要用到open、create返回该文件描述符
1.2文件描述符的拷贝和重定向
#include <unistd.h>
int dup(int oldfd);
参数:
oldfd:要拷贝的文件描述符
返回值:成功返回新的文件描述符,失败-1 。
文件描述符的重定向(捆绑)
int dup2(int oldfd, int newfd);
参数:
oldfd,newfd:文件描述符
返回值:成功返回newfd的值,失败返回-1 ;
注意:
在dup2之前关闭newfd描述的文件
2.程序开机自启动的三个文件
所以创建文件,其文件描述符从3开始排序
3.虚拟文件系统
Linux系统成功的关键因素之一就是具有与其他操作系统和谐共存的能力。Linux的文件系统由两层架构构成:虚拟文件操作系统(VFS),和各种具体的文件系统。
VFS就是将各种具体的文件系统的公共部分抽取出来,形成抽象层,是系统内核的一部分。通过VFS就对用户屏蔽了底层文件系统的实现的细节和差异。
可以通过命令查看系统支持哪些文件系统:
cat /proc/filesystems
VFS在Linux系统中位置:
4.文件IO的操作
4.1文件io的打开
打开文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
参数:
pathname:文件路径
flags:打开方式
O_RDONLY :只读
O_WRONLY :只写
O_RDWR :读写 这三个参数互斥
-------------------------------------------------
O_CREAT :如果文件不存在则创建,并使用第三个参数设置权限
O_EXCL :在使用O_CREAT创建文件的时候,如果文件已经存在,则返回错误信息,
可以用来检测文件是否存在
O_APPEND:以追加方式打开文件
O_TRUNC :如果文件已经存在,那么打开文件时先删除文件原有的数据。
mode:创建文件的权限
mode = mode & (~uamsk)
返回值:成功返回文件描述符,失败返回-1
r: O_RDONLY
r+: O_RDWR
w: O_WRONLY|O_CREAT|O_TRUNC,0666
w+: O_RDWR|O_CREAT|O_TRUNC,0666
a: O_WRONLY|O_CREAT|O_APPEND,0666
a+: O_RDWR|O_CREAT|O_APPEND,0666
4.2文件IO的读写
1>读:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
fd:文件描述符
buf:内存首地址
count:实际读取的个数,最多只能读buf-1 个字符
返回值:成功返回实际读取到的个数,到达文件末尾返回0,
失败返回-1 ,并且设置错误号。
2>写:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd:文件描述符
buf:内存首地址
count:实际写入的个数,最多只能读buf-1 个字符
返回值:成功返回实际写入到的个数,失败返回-1 ,并且设置错误号。
4.3文件的关闭
#include <unistd.h>
int close(int fd);
4.4文件的定位
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
fd:文件描述符
offset:相对于whence的偏移量
whence:偏移的起始位置
SEEK_SET, 文件的起始位置
SEEK_CUR, 文件指针当前的位置
SEEK_END, 文件的末尾
返回值:成功返回偏移量,失败返回-1
5文件锁
当文件已经共享,多个文件操作同一文件,有时会存在资源竞争的问题,Linux通常采用给文件上锁来解决。
1>fcntl函数:可以施加建议性锁,强制性锁,还有记录锁 。
可以对各种打开文件进行操作,不仅可以管理文件锁,还可以获取和设置文件相关标志位,以及复制文件描述符
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
fd文件描述符
cmd:
F_GETLK:检查文件锁状态
F_SETLK:设置lock描述的文件锁
F-SETLKW:对F_SETLK的阻塞版本(W表述wait())
返回值:
成功返回 0
出错 -1
如果cmd和锁操作相关,则第三个参数类型为struct *flock其定义如下:
struct flock {
...
short l_type; /* Type of lock: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* How to interpret l_start:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* Starting offset for lock */
off_t l_len; /* Number of bytes to lock */
pid_t l_pid; /* PID of process blocking our lock
(set by F_GETLK and F_OFD_GETLK) */
...
};
flock结构体成员含义:
l_type:
F_RDLCK 读取锁(共享锁)
F_WRLCK 写入锁(排斥锁)
F_UNLCK 解锁
l_start
加锁区域在文件中相对位移量(字节),与lwhence值一起决定加锁区域的起始位置
l_len
加锁区域长度
l_pid
具有阻塞当前进程的锁,其持有的进城后的进程号存放在l_pid中,仅由F_GETLK返回。
三、Linux一切皆文件
1.1文件属性
相对函数进行具体了解可以 man +函数名。如果在编程时需要查看函数可直接将光标放到函数名字上,再 ESC后,再:shift+k跳转到相对的函数库。按两次q回到vim编写函数。
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);
三个函数的返回:若成功则为0,若出错则为-1,并且设置errno.
给定一个pathname的情况下:
stat函数返回一个与此命名文件有关的信息结构
fstat函数获得已在描述符filedes上打开的文件的有关信息
lstat函数类似于stat,但是当命名的文件是一个符号连接时,
lstat返回该符号连接的有关信息,而不是由该符号连接引用
的文件的信息。
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number inode编号*/
mode_t st_mode; /* File type and mode 文件类型以及权限*/
nlink_t st_nlink; /* Number of hard links 硬链接数*/
uid_t st_uid; /* User ID of owner 文件拥有者的用户ID*/
gid_t st_gid; /* Group ID of owner 文件所属组的组ID*/
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes 文件大小 */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access 最后一次访问时间*/
struct timespec st_mtim; /* Time of last modification 最后一次修改时间*/
struct timespec st_ctim; /* Time of last status change 最后一次状态改变时间 */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
//通过用户ID获得用户信息
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd {
char *pw_name; /* username */
char *pw_passwd; /* user password */
uid_t pw_uid; /* user ID */
gid_t pw_gid; /* group ID */
char *pw_gecos; /* user information */
char *pw_dir; /* home directory */
char *pw_shell; /* shell program */
};
//通过组ID获得组的信息
#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
struct group {
char *gr_name; /* group name */
char *gr_passwd; /* group password */
gid_t gr_gid; /* group ID */
char **gr_mem; /* NULL-terminated array of pointers
to names of group members */
};
2.目录操作
目录也是文件:可以对目录进行打开关闭,读写操作。
1. 打开目录
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
参数:
name:目录名字
返回值:成功返回DIR类型的指针,失败返回NULL
2. 读取目录
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
返回值:成功返回一个结构体指针,失败返回NULL
struct dirent {
ino_t d_ino; /* Inode number 索引的节点号*/
off_t d_off; /* Not an offset; see below 在目录文件中的偏移*/
unsigned short d_reclen; /* Length of this record 文件名的长度*/
unsigned char d_type; /* Type of file; not supported
by all filesystem types 文件类型*/
char d_name[256]; /* Null-terminated filename 文件名*/
};
3. 关闭目录
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
4. 修改程序当前的工作目录
#include <unistd.h>
int chdir(const char *path);
参数:
path:程序运行更改后的目录
返回值:成功返回0,失败-1 ,并且设置错误号
下一篇文章:多进程的生老病死,收尸,不想死