分类 linux编程学习 下的文章

linux编程学习 01 linux IO 系统调用

一、文件I/O系统调用简介

1、标准库函数

遵守ISO标准,基于流的I/O,对文件指针(FILE结构体)进行操作

2、系统调用

兼容POSIX标准,基于文件描述符的I/O,对文件描述符进行操作,包括:
open(): 打开文件
create():创建文件
close(): 关闭文件
read(): 读取文件
write(): 写入文件
lseek(): 文件定位
这些不带缓存的函数都是内核提供的系统调用。它们不是ANSI C的组成部分,但是POSIX的组成部分。

二、文件描述符

1、对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述符标识该文件,将其作为参数传送给read或write。
2、在POSIX应用程序中,整数0、1、2被替换成符号常数 STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常数都定义在头文件 中。
3、文件描述符的范围是0-OPEN_MAX,早期的unix版本是19(允许每个进程打开20个文件),现在很多系统设置成23,linux为1024

标准文件指针

  • stdin 0
  • stdout 1
  • stderr 2

fdopen()

FILE *fdopen(int fd,const char* mode);

文件描述符 -----> 文件指针 (fd -----> FILE*)

fileno

int fileno(FILE *stream)

文件指针 -----> 文件描述符 (FILE* -----> fd)

三,函数介绍

一、open函数

1、 头文件(后面都是这个格式)

#include <sys/types.h>.        // 提供mode_t类型
#include <sys/stat.h>          // 提供open()函数的符号
#include <fcntl.h>             // 提供open()函数
#include <unistd.h>            // 提供close()函数
#include <errno.h>             // 提供strerror()函数和errno
#include <stdio.h>             // 提供输出函数
#include <string.h>            // 提供strerror函数

2、函数原型

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

返回: 若成功为文件描述符,若出错为-1
功能: 打开或创建一个文件
参数
pathname:要打开或者创建的文件路径
flags:用来说明此函数的多个选择项
用下列一个或者多个常数进行按位或运算构成flags参数,这些常数定义在/usr/include/bits/fcntl.h中,在include fcntl.h时,会包含这个文件

常量名解释
O_RDONLY只读
O_WRONLY只写
O_RDWR读写
O_CREAT如果指定的文件不存在,则按照mode参数指定的文件权限来创建文件
O_EXCL如果同时指定了O_CREAT,而文件已经存在,则出错。可测试一个文件是否存在。但在网络文件系统进行操作时却没有保证
O_DIRECTORY如果参数pathname不是一个目录,则open出错
O_TRUNC如果文件存在,而且为只读或只写成功打开,则将其长度截短为0
O_APPEND以追加模式打开文件,每次写时加到文件尾端,但在网络文件系统进行操作时没有保证
O_NONBLOCK如果pathname指定的是是一个FIFO,一个块特殊文件或者一个字符特殊文件,则此选择项为此文件的本次打开操作和后续I/O操作设置非阻塞方式

mode:新建文件的访问权限,对于open函数而言,仅当创建新文件时才使用第三个参数。

二、creat函数

int creat(const char *pathname, mode_t mode);
返回: 若成功为只写打开的文件描述符,若出错为-1
功能: 创建一个新文件
等同于open (pathname, O_WRONLY | O_CREAT | O_TRUNC, mode) ;
以只写方式打开所创建的文件。

三、close函数

int close(int fd);

返回: 若成功为0,若出错为-1
功能: 关闭一个打开的文件
参数
fd:已打开文件的文件描述符
当一个进程终止时,他所有打开的文件都由内核自动关闭

四、read函数

ssize_t read(int fd, void *buf, size_t count);
返回: 读到的字节数,若已到文件尾为0,若出错为-1
功能: 从打开文件中读数据
参数:
fd:读取文件的文件描述符
buf:存放读取数据的缓存
count:要求读取一次数据的字节数

有多种情况可使实际读到的字节小于要求读取的字节数

  • 读普通文件时,在读到要求字节数之前已经到达文件尾端
  • 当从终端设备读取时,通常一次只能读取一行
  • 当从网络读时,网络中的缓冲机构可能造成返回值小于要求要读的字节数
  • 某些面向记录的设备,如磁带,一次最多返回一个记录
  • 进程由于信号造成中断

读操作从当前位移量处开始,在成功返回之前,该位移量增加实际读到的字节数==

五、write函数

ssize_t write(int fd, const void *buf, size_t count);
返回: 若成功为已写的字节数,若出错为-1
功能: 向打开的文件中写数据
参数:
fd:写入文件的文件描述符
buf:存放待写数据的缓存
count:要求写入一次数据的字节数

其返回值通常与参数count的值相同,否则表示出错
write出错的原因:磁盘已满,或者超过了对一个给定进程的文件见长度限制
对于普通文件,写操作从文件当前位移量处开始。如果在打开该文件时,指定了O_APPEND选择项,则在每次写操作前,将文件位移量设置在文件的当前结尾处。在一次写成功之后,该文件位移量增加实际写的字节数

示例

int cp(char* source,char* destination)
{
        int s = open(source,O_RDONLY);
        int d = open(destination,O_CREAT | O_WRONLY | O_TRUNC,0777);
        if(s == -1 || d == -1)
        {
                fprintf(stderr,"open file error %s \n",strerror(errno));
                return 0;
        }
        // 每次读取的大小可以根据设备的block_size来决定,可以通过 tune2fs -l /dev/sda1 查看
        int buffer_length = 888,read_flag = 0;
        char buffer[buffer_length];
        while((read_flag = read(s,buffer,buffer_length)) != 0)
        {
                // 只写入读取到的数据
                if(read_flag > write(d,buffer,read_flag))
                {       
                        fprintf(stderr,"write file error %s \n",strerror(errno));
                        return 0;
                }
                memset(buffer,'\0',buffer_length);
        }
        close(s);
        close(d);
        return 1;
}

lseek函数

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

  • 头文件:
#include <sys/types.h>
#include <unistd.h>
  • 返回:若成功则返回新的文件位移量(绝对偏移量);
  • 功能:定位一个以打开的文件
  • 参数

    • fd:已打开文件的文件描述符
    • offset:位移量
    • whence:定位的位置

      • SEEK_SET:将该文件的位移量设置为距文件开始处offset个字节
      • SEEK_CUR:将该文件的位移量设置为其当前值加offset,offset可为正或负
      • SEEK_END: offset,offset可为正或负
  • lseek也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。
  • 每个打开文件都有一个与其相关联的"当前文件偏移量"。它是一个非负整数,用以度量从文件开始处计算的字节数。通常,读,写操作都从当前文件偏移量处开始,并将偏移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选择项,否则该位移量被设置为0.
  • 示例
// 创建黑洞文件
int file_hole(char* file_path)
{       
        int f = open(file_path,O_CREAT | O_WRONLY | O_TRUNC,0777);
        if(f == -1)
        {       
                perror("open or create file:");
                return 0;
        }
        char buffer[] = "abcdef";
        if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
        {       
                perror("write file:");
                return 0;
        }
        // 移出文件末尾,再进行写入
        lseek(f,8,SEEK_END); 
        if(sizeof(buffer) != write(f,buffer,sizeof(buffer)))
        {       
                perror("write file:");
                return 0;
        }
        close(f);
        return 1;
}
// cat函数
void cat(int argc,char* argv[])
{
        int in = STDIN_FILENO;
        int out = STDOUT_FILENO;
        if(argc == 1)
        {   
                cp_by_file_no(in,out);
        }   
        else
        {   
                for(int i = 1;i < argc;i++)
                {   
                        in = open(argv[i],O_RDONLY);
                        if(in == -1) 
                        {   
                                perror("open file error:");
                        }   
                        cp_by_file_no(in,out);
                }   
        }   
}

文件I/O的内核数据结构

  • 一个打开的文件在内核中使用三种数据结构(结构体)表示

    • 文件描述符表(使用open等函数返回的文件描述符其实就是文件描述表(结构体数组)的下表)

      • 文件描述符标志
      • 文件表项指针
    • 文件表项

      • 文件状态标志

        • 读,写,追加,同步和非阻塞等状态标志
      • 当前文件偏移量
      • i节点表项指针
      • 饮用计数器
    • i节点

      • 文件类型和对该文件的操作函数指针
      • 当前文件长度
      • 文件所有者
      • 文件所在的设备,文件访问权限
      • 指向文件数据在磁盘块上所在位置的指针等

dup与dup2函数

int dup(int oldfd);
int dup2(int oldfd,int newfd);

  • 头文件:
    #include <unistd.h>
  • 返回:成功返回新文件描述符,出错返回-1
  • 功能:文件描述符的复制
  • 参数

    • oldfd:原先的文件描述符
    • newer:新的文件描述符
  • 由dup返回的新文件描述符一定时当前可用文件描述符中的最小数值
  • 用dup2则可以用newfd参数指定新描述符的数值。如newfd已经打开,则先将其关闭。如若oldfd等于newfd,则dup2返回newfd,而不关闭它(就是把文件描述符表中下标为newfd的结构体中文件表项指针存放的地址切换成文件描述符表中下标为oldfd的结构体中文件表项指针存放的地址,就是指针间的赋值)
  • 在进程间通信时可用来改变进程的标准输入和标准输出设备
  • 示例
void cat(int argc,char* argv[])
{
        int flag_arr[4] = {-1,-1,-1,-1},i = 1,stdin_temp = -1,stdout_temp = -1,file_temp = -1,argc_temp = argc;
        if(argc > 2)
        {
                for(;i < (argc_temp - 1);i++)
                {
                        // 修改输入重定向
                        if(strcmp("+",argv[i]) == 0)
                        {
                                flag_arr[0] = i++;
                                flag_arr[1] = i;
                                // 备份下标准输入,函数执行完毕后恢复
                                stdin_temp = dup(STDIN_FILENO);
                                file_temp = open(argv[i],O_RDONLY);
                                if(file_temp == -1)
                                {
                                        perror("open input file error");
                                }
                                dup2(file_temp,STDIN_FILENO);
                                argc -= 2;
                        }
                        // 修改输出重定向
                        if(strcmp("-",argv[i]) == 0)
                        {
                                flag_arr[2] = i++;
                                flag_arr[3] = i;
                                // 备份下标准输入,函数执行完毕后恢复
                                stdout_temp = dup(STDOUT_FILENO);
                                file_temp = open(argv[i],O_CREAT | O_WRONLY | O_TRUNC,0777);
                                if(file_temp == -1)
                                {
                                        perror("create or open output file error");
                                }
                                dup2(file_temp,STDOUT_FILENO);
                                argc -= 2;
                        }
                }
        }
        // 同时输入了输入和输出
        if(flag_arr[0] != -1 && flag_arr[2] != -1)
        {
                argc = flag_arr[1] + 1;
                flag_arr[1] = -1;
        }
        int in = STDIN_FILENO;
        int out = STDOUT_FILENO;
        if(argc == 1)
        {
                cp_by_file_no(in,out);
        }
        else
        {
                for(i = 1;i < argc;i++)
                {
                        if(in_array(i,flag_arr,4) == 1)
                        {
                                continue;
                        }
                        in = open(argv[i],O_RDONLY);
                        if(in == -1)
                        {
                                perror("open file error:");
                        }
                        cp_by_file_no(in,out);
                }
        }
        // 恢复标准输入输出
        if(flag_arr[0] != -1)
        {
                dup2(stdin_temp,STDIN_FILENO);
        }
        if(flag_arr[2] != -1)
        {
                dup2(stdout_temp,STDOUT_FILENO);
        }
}

fcntl函数

int fcntl(int fd,int cmd);
int fcntl(int fd,int cmd,long arg);
int fcntl(int fd,int cmd,struct flock *lock);

  • 头文件:
#include <unistd.h>
#include <fcntl.h>
  • 返回:若成功则依赖于cmd,若出错为-1
  • 功能:可以改变已经打开文件的性质
  • cmd的常见取值

    • F_DUPFD

      • 复制文件描述符,新的文件描述符作为函数值返回
    • F_GETFD/F_SETFD

      • 获取/设置文件描述符,通过第三个参数设置
    • F_GETFL/F_SETFL

      • 获取/设置文件描述符标志,通过第三个参数设置
      • 可以更改的几个标志是:O_APPEND,O_NONBLOCK,SYNC,O_ASYNC(O_RDONLY,O_WRONLY,O_RDWR不适用)
  • 常见使用

    • 复制一个现存的描述符,新文件描述符作为函数值返回(cmd=F_DUPFD)
    • 获得/设置文件描述符标志(cmd=F_GETFD或F_SETFD)
    • 获得/设置文件状态标志(cmd=F_GETFL或F_SETFL)
    • 获得/设置文件锁(cmd=F_SETLK,cmd=F_GETLK、F_SETLKW)

      • 文件锁必须用到第三个参数,第三个参数为struck flock结构体指针
  • 示例
int set_fl(int fd,int flag)
{
        flag |= fcntl(fd,F_GETFL);
        if(fcntl(fd,F_SETFL,flag) < 0)
        {
                return 0;
        }
        return 1;
}
int clear_fl(int fd)
{
        int flag = fcntl(fd,F_GETFL);
        flag &= ~flag;
        if(fcntl(fd,F_SETFL,flag) < 0)
        {
                return 0;
        }
        return 1;
}

I/O处理方式

  • I/O阻塞模型

    • 若所调用的I/O函数没有完成相关功能就会使进程挂起,直到相关数据到达才会返回。如:终端,网络设备的访问
  • 非阻塞模型

    • 当请求的I/O操作不能完成时,则不让进程休眠,而且返回一个错误。如open,read,write访问
  • I/O多路转接模型

    • 如果请求的I/O操作阻塞,且她不是真正阻塞I/O,而且让其中的一个函数等待,在这期间,I/O还能进行其他操作。如:select函数。
  • 信号驱动I/O模型

    • 在这种模型下,通过安装一个信号处理程序,系统可以自动捕获特定信号的到来,从而启动I/O。
  • 异步I/O模型

    • 在这种模型下,当一个描述符已准备好,可以启动I/O时,进程会通知内核。有内核进行后续处理,这种用法现在较少。
        char str[888] = {'\0'};
        // 设置非阻塞
        set_fl(STDIN_FILENO,O_NONBLOCK);
        // 下面的代码不会等待
        read(STDIN_FILENO,str,888);
        printf("%s\n",str);

文件和目录

struct stat结构体

struct stat{
    mode_t    st_mode;    /* file type & permission */
    ino_t     st_ino;     /* i-node number */
    dev_t     st_dev;     /* device number(file system) */
    dev_t     st_rdev;    /* device number for special files */
    nlink_t   st_nlink;   /* number of links */
    uid_t     st_uid;     /* user ID of owner */
    gid_t     st_gid;     /* group ID of owner */
    off_t     st_size;    /* size in byte */
    time_t    st_atime;   /* time of last access */
    time_t    st_mtime;   /* time of last modification */
    time_t    st_ctime;   /* time of last file status change */
    blksize_t st_blksize; /* best I/O block size */
    blkcnt_t  st_blocks;  /* number of disk blocks allocated */
};

文件属性操作函数

int stat(const char* pathname,struct stat* buf);
int fstat(int fd,struct stat* buf);
int lstat(const char* pathname,struct stat* buf);

  • 头文件:
#include <sys/types.h>
#include <sys/stat.h>
  • 返回:成功返回0,出错返回-1
  • 功能:返回一个pathname路径或fd指定的文件的属性信息,存储在结构体buf中
  • 参数

    • pathname:文件路径名字
    • buf:struct stat结构体指针
  • lstat类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息

linux中七种文件类型和七种宏

文件类型
普通文件(regular file)S_ISREG()
目录文件(directory file)S_ISDIR()
块特殊文件(block special file)S_ISBLK()
字符特殊文件(character special file)S_ISCHR()
管道文件 FIFO(named pipe)S_ISFIFO()
套接字(socket)S_ISSOCK()
符号链接(symbolic link)S_ISLNK()
  • 示例
void file_type(char* path)
{
        struct stat s;
        if(lstat(path,&s) == -1)
        {       
                perror("incorrect file");
                return;
        }
        char* str = "unknow type";
        if(S_ISREG(s.st_mode))
        {       
                str = "regular file";
        }
        else if(S_ISDIR(s.st_mode))
        {       
                str = "directory";
        }
        else if(S_ISBLK(s.st_mode))
        {
                str = "block special file";
        }
        else if(S_ISCHR(s.st_mode))
        {
                str = "character special file";
        }
        else if(S_ISFIFO(s.st_mode))
        {
                str = "FIFO";
        }
        else if(S_ISSOCK(s.st_mode))
        {
                str = "socket file";
        }
        else if(S_ISLNK(s.st_mode))
        {
                str = "symbolic link";
        }
        printf("%s : %s\n",path,str);
}
void print_file_stat(char* path)
{
        struct stat* s = malloc(sizeof(struct stat));
        if(stat(path,s) == -1)
        {
                perror("incorrect file : ");
        }
        printf("mode \t %d\nino \t %d\ndev \t %d\nrdev \t %d\nnlink \t %d\nuid \t %d\ngid \t %d\nsize \t %d\natime \t %d\nmtime \t %d\nctime \t %d\nblksize \t %d\nblocks \t %d\n",
                (int)s->st_mode,(int)s->st_ino,(int)s->st_dev,(int)s->st_rdev,(int)s->st_nlink,(int)s->st_uid,(int)s->st_gid,(int)s->st_size,(int)s->st_atime,(int)s->st_mtime,(int)s->st_ctime,(int)s->st_blksize,(int)s->st_blocks);
        free(s);
}

文件权限

  • 9种文件访问权限位

    • 用户权限

      • S_IRUSR,S_IWUSR,S_IXUSR
    • 组权限

      • S_IRGRP,S_IWGRP,S_IXGRP
    • 其他权限

      • S_IROTH,S_IWOTH,S_IXOTH
  • 文件权限通过按位或方式构造
  • 示例:
// 创建文件是给权限
int f1 = open("test.txt",O_CREAT | O_RDONLY,S_IWUSR | S_IRUSR | S_IWGRP);
int f2 = open("test.txt",O_CREAT | O_RDONLY,0620); // 与上面效果一致
// 输出文件权限
void file_permission(char* path)
{
        struct stat s;
        if(lstat(path,&s) == -1)
        {
                perror("incorret file");
                return;
        }
        printf("%c%c%c%c%c%c%c%c%c\n"
                ,(S_IRUSR & s.st_mode) ? 'r' : '-'
                ,(S_IWUSR & s.st_mode) ? 'w' : '-'
                ,(S_IXUSR & s.st_mode) ? 'x' : '-'
                ,(S_IRGRP & s.st_mode) ? 'r' : '-'
                ,(S_IWGRP & s.st_mode) ? 'w' : '-'
                ,(S_IXGRP & s.st_mode) ? 'x' : '-'
                ,(S_IROTH & s.st_mode) ? 'r' : '-'
                ,(S_IWOTH & s.st_mode) ? 'w' : '-'
                ,(S_IXOTH & s.st_mode) ? 'x' : '-'
        );
}

access函数

int access(const char* pathname, int mode);

  • 返回:成功执行时返回0,若出错为-1
  • 功能:检查是否可以对指定文件进行某种操作
  • 头文件
    #include <unistd.h>
  • 参数

    • pathname:文件路径
    • mode:文件访问权限

      • R_OK:判断文件是否有读权限
      • W_OK:判断文件是否有写权限
      • X_OK:判断文件是否有可执行权限
      • F_OK:判断文件是否存在
  • 示例
void file_access(char* path)
{
        if(access(path,F_OK) == -1)
        {
                printf("%s does not exist\n",path);
                return;
        }
        if(access(path,R_OK) == -1)
        {
                printf("%d cannot read file:%s\n",getpid(),path);
        }       
        if(access(path,W_OK) == -1)
        {
                printf("%d cannot write file:%s\n",getpid(),path);
        }
        if(access(path,X_OK) == -1)
        {
                printf("%d cannot excute file:%s\n",getpid(),path);
        }
}

linux文件系统的结构

  • 文件操作相关的基本元素是:目录结构,索引节点和文件的数据本身

    • 目录结构(目录项)
    • 索引节点(i节点)
    • 文件的数据
  • 文件系统(硬盘)的三个区域

    • 超级块:存放文件系统本身的结构信息
    • i-节点表:存放i节点信息列表
    • 数据区:存放文件内容

软链接与硬链接的区别

硬链接:会创建新目录项并指向原文件的i节点,并且文件的链接数会加1
软链接:会创建新目录项,新i节点,新数据块,新数据块存放的是原文件的路径

link函数

int link(const char* existing path,const char* newpath);

  • 返回:成功返回0,出错返回-1
  • 功能:创建一个指向现存文件链接(硬链接)
  • 创建条件

    • 针对文件创建链接
    • 必须是同一分区
    • 只有超级用户才能对目录建立链接
  • 示例
if(-1 == link(argv[1],argv[2]))
{
        printf("fail to create %s\n",argv[2]);
        return 1;
}

unlink函数

int link(const char* existing path,const char* newpath);

  • 返回:成功返回0,出错返回-1
  • 功能:删除pathname指定的硬链接,并将由pathname所引用的文件链接计数减1
  • 文件删除条件

    • 链接计数为0
    • 无其他进程打开该文件
  • 示例
if(argc < 2)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
if(-1 == unlink(argv[1]))
{
        printf("fail to delete %s\n",argv[1]);
        return 1;
}

remove函数

int remove(const char* pathname);

  • 返回:成功返回0,出错返回-1
  • 功能:解除对一个文件或目录的链接
  • 头文件
#include <unistd.h>
  • 对于文件,remove的功能与unlink相同
  • 对于目录,remove的功能与rmdir相同

rename函数

int rename(const char* oldname,const char* newname);

  • 返回:成功返回0,出错返回-1
  • 功能:解除对一个文件或目录的链接
  • 头文件
#include <unistd.h>

symlink函数

int symlink(const char* actualpath,const char* sympath);

  • 返回:成功返回0,出错返回-1
  • 功能:创建一个符号链接(软链接)
  • 头文件
#include <unistd.h>
  • 创建符号链接并不要求actualpath存在
  • 可以跨文件系统建立符号链接
  • 示例
if(argc < 3)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
if(access(argv[2],F_OK) == 0)
{
        printf("%s has alreay existed\n",argv[2]);
        return 1;
}
if(-1 == symlink(argv[1],argv[2]))
{
        printf("fail to create %s\n",argv[2]);
        return 1;
}

readlink函数

int readlink(const char * path ,char * buf,size_t bufsiz);

  • 返回:成功返回0,出错返回-1
  • 功能:打开该链接本身,并读该链接中的名字
  • 头文件
#include <unistd.h>
  • 示例
if(argc < 2)
{
        printf("incorrect parameter\n");
        return 1;
}
if(access(argv[1],F_OK) == -1)
{
        printf("%s does not exist\n",argv[1]);
        return 1;
}
const int buffer_size = 1024;
char buffer[1024] = {'\0'};
if(-1 == readlink(argv[1],buffer,buffer_size))
{
        printf("fail to delete %s\n",argv[1]);
        return 1;
}
write(STDOUT_FILENO,buffer,buffer_size);
write(STDOUT_FILENO,"\n",1);