[学习笔记]达内纪录片day22 linux系统调用文件管理

    选择打赏方式

一、系统调用文件管理函数

打开/创建文件
打开已有的文件或创建新文件
#include<fcntl.h>
int open(const char *pathname,int flags,mode_t mode)

参数:
pathname:指定要打开的文件的名字
flags:
三选一:
O_RDONLY    只读 
O_WRONLY    只写
O_RDWR     可读可写
可以按位或到flags中  文件的创建标记  状态标记
O_CREAT   如果文件不存在,创建文件。使用第三个参数指定文件的权限。

mode文件最终的权限是mode&~umask.

如果文件存在:
O_EXCL  如果文件存在,和O_CREAT一起使用的时候,报错
O_TRUNC  如果文件存在,将文件的内容清空
O_APPEND  以追加的方式打开文件

...:mode  如果flags中有标记O_CREAT,必须提供mode。mode & ~umask

返回值:
成功   文件描述符
失败  -1   errno被设置


仅创建文件

#include<fcntl.h>
int creat(const char *pathname,mode_t mode)

参数:

pathname:指定要创建的文件名

mode:指定创建的文件权限,最终权限为mode & ~umask

返回值:

成功  文件描述符

失败  返回-1 errno被设置

仅打开文件
#include<fcntl.h>
int open(const char *pathname,int flags)

参数:

pathname:要打开的文件名

flags:标志

返回值:

成功  文件描述符

失败  -1 errno被设置


向文件写入数据

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

参数:
fd:指定了目标文件
buf:将buf指定的地址空间里的数据写到文件中
count:请求写入的最大数
返回值:
成功  实际写入的字节数
失败  -1  errno被设置



重新定位文件的读写位置
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
fd:   指定了具体的要操作的文件
offset:指定一个位置
whence:
SEEK_SET   文件的读写位置定位在0ffset字节处
SEEK_CUR   文件的读写位置定位在当前位置+offset字节处
SEEK_END   
返回值:
成功  返回距离文件开头的字节数
失败  (off_t) -1  errno被设置

关闭文件

关闭处于打开状态的文件描述符

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

参数:

fd:要关闭的文件描述符

返回值:

成功  0

失败  -1 errno被设置


头文件封装

<t_file.h>

def T_FILE_H_
#define T_FILE_H_
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <t_stdio.h>
#include <unistd.h>
#endif


以只读方式打开:

#include <t_file.h>
int main(int argc,char *argv[]){
    //以只读的方式打开文件
    int fd=open(argv[1],O_RDONLY);
    if(fd==-1)E_MSG("open",-1);
    printf("open file %s success...\n",argv[1]);
    //关闭文件描述符
    close(fd);
    return 0;
}


如果文件存在,报错,文件不存在,创建,并给予权限:

#include <t_file.h>
int main(int argc,char *argv[]){
    int flags=O_WRONLY|O_CREAT|O_EXCL;
    //mode_t mode=0644;
    mode_t mode=S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;
    int fd=open(argv[1],flags,mode);
    if(fd==-1)E_MSG("open",-1);
    printf("open success...\n");
    close(fd);
    return 0;
}


运行结果:

TIM截图20190801014237.png

类似cat的功能:

#include <t_file.h>
int main(int argc,char *argv[]){
    char buf[128];
    int r;
    //打开文件  argv[1] 只读
    int fd=open(argv[1],O_RDONLY);
    if(fd==-1)E_MSG("open",-1);  
    while((r=read(fd,buf,128))>0)
            write(1,buf,r);
    close(fd);
    return 0;
}


运行结果:

TIM截图20190801013749.png



文件的权限和类型

linux系统中通过 “ls -al”,可查看当前目录的所有文件的详细信息。

信息分为7列,如下图所示:

TIM截图20190730230730.png

具体含义如下图

640.png

第一列为文件类型和权限

640 (1).png


第一个字符表示文件类型:

【d】:表示目录文件


【-】:表示普通文件


【l】:表示链接文件(linkfile)


【b】:表示设备文件里可供存储的接口设备


【c】:表示设备文件里的串行端口设备,如键盘鼠标。


第二列表示有多少文件名连接到此节点(i-node);

每个文件都会将它的权限与属性记录到文件系统的i-node中,不过我们使用的目录树却是使用文件名来记录,因此,每个文件名就会连接到一个i-node。这个属性记录有多少个不同的文件名连接到相同的一个i-node节点。


第三列表示这个文件(或目录)的 “所有者账号”;


第四列表示这个文件的所属用户组;

在Linux系统中,你的账号会附属于一个或者多个用户的用户组中,这个用户组就是所属用户组。


第五列表示文件大小,默认单位为B


第六列为这个文件的创建日期或者最近修改日期


第七列为该文件名。


2.修改文件权限

常见的修改用户权限的命令如下:

1. chgrp:改变文件所属用户组

2. chown:改变文件所有者

3. chmod:改变文件权限


改变所属用户组chgrp

改变所属用户组很简单,直接使用chgrp xxx。注意所改变的用户组名必须要在/etc/group文件内存在才行,否则会报错。


改变文件所有者chown

chown [-R] 用户名称 文件或者目录

chown [-R] 用户名称: 组名 文件或者目录

-R:表示递归更改,即连同子目录下的所有文件


改变权限chmod

1. 数字类型修改

chmod [-R] xyz 文件或目录

-R :递归执行

xyz:数字类型的权限属性(r:4 w:2 x:1)

chmod 770  a.c  (rwxrwx---)


2. 符号类型修改

u: user

g: group

o: others

a: all(全部身份)


linux文件类型

普通文件(-)
我们用 ls -lh 来查看某个文件的属性,可以看到有类似 -rw-r--r-- ,值得注意的是第一个符号是 - ,这样的文件在Linux中就是普通文件。这些文件一般是用一些相关的应用程序创建,比如图像工具、文档工具、归档工具... .... 或 cp工具等。这类文件的删除方式是用rm 命令

目录(d)

当我们在某个目录下执行,看到有类似 drwxr-xr-x ,这样的文件就是目录,目录在Linux是一个比较特殊的文件。注意它的第一个字符是d。创建目录的命令可以用 mkdir 命令,或cp命令,cp可以把一个目录复制为另一个目录。删除用rm 或rmdir命令。
字符设备(c)和块设备(b)

这个种类的文件,是用mknode来创建,用rm来删除。目前在最新的Linux发行版本中,我们一般不用自己来创建设备文件。因为这些文件是和内核相关联的。
套接口文件(s)
注意这个文件的属性的第一个字符是 s。我们了解一下就行了
符号链接(l)
当我们查看文件属性时,会看到有类似 lrwxrwxrwx,注意第一个字符是l,这类文件是链接文件。是通过ln -s 源文件名 新文件名 。上面是一个例子,表示setup.log是install.log的软链接文件。怎么理解呢?这和Windows操作系统中的快捷方式有点相似。以上介绍Linux文件类型。

管道(p)

管道文件:FIFO也是一种特殊的文件类型,它主要的目的是,解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写。第一个属性为 [p]


权限掩码


在linux下创建一个文件或者目录之后是可以通过chmod等命令进行权限设置,来达到给当前用户、用户组用户以及其他用户分配不同的访问权限。那么,我们新创建的目录和文件本身也是有它的默认权限的,这个默认权限是什么,就是由权限掩码umask所确定的。它的功能可以说与chmod刚好相反的,代表默认拿走的也就是说不要的权限。

掩码值的计算方法:
1、将默认最大权限(目录777,文件666)和umask值都转换为2进制
2、对umask取反
3、将默认权限和umask取反后的值做与运算
4、将得到的二进制值再转换八进制,即为权限
例1: umask(显示、设置文件的缺省权限命令) 为022
文件默认最大权限:6 6 6 二进制为:110 110 110
umask值 0 2 2 二进制为: 000 010 010
一、将umask的值 000 010 010 取反得: 111 101 101
二、默认权限的值110 110 110 与umask取反后的值111 101 101做与运算得110 100 100
三、将110 100 100 转成八进制得6 4 4,即为权限


关于权限chmod的0:https://chen498402552-163-com.iteye.com/blog/1164407

注意:-open函数之所以存在多种参数表的形式,并非源自类似C++语言的函数重载机制,而是使用了标准C语言提供的可变长参数列表,手册中的写法只是为了便于阅读和使用

可变参数参考资料:http://c.biancheng.net/view/344.html

二、文件的内核结构

一个处于打开状态的文件,系统会为其在内核中维护一套专门的数据结构,保存该文件的信息,直到它被关闭。


v节点与v节点表
文件的元数据和在磁盘上的存储位置都保存在其i节点中,而i节点保存在分区柱面组的i节点表中,在打开文件时将其i节点信息读入内存,并辅以其它的必要信息形成一个专门的数据结构,势必会提高对该文件的访问效率,这个存在于进程的内核空间,包含文件i节点信息的数据结构被称为v节点。
多个v节点结构以链表的形式构成v节点表


文件表项与文件表
由文件状态标志(来自open函数的flags参数)、文件读写位置(最后一次读写的最后一个字节的下一个位置)和v节点指针等信息组成的内核数据结构被称为文件表项。通过文件表项一方面可以实时记录每次读写操作的准确位置,另一方面可以通过v节点指针访问包括该文件各种元数据和磁盘位置在内的i节点信息。多个文件表项以链表的形式构成文件表


多次打开同一个文件,无论是在同一个进程中还是在不同的进程中,都只会在系统内核中产生一个v节点

每次打开文件都会产生一个新的文件表项,各自维护各自的文件状态标志和当前文件偏移,却可能因为打开的是同一个文件而共享同一个v节点

打开一个文件意味着内存资源(v节点、文件表项等)的分配,而关闭一个文件其实就是为了释放这些资源,但如果所关闭的文件在其它进程中正处于打开状态,那么其V节点并不会被释放,直到系统中所有曾打开过该文件的进程都显式或隐式地将其关闭,其v节点才会真正被释放

一个处于打开状态的文件也可以被删除,但它所占用的磁盘空间直到它的v节点彻底消失以后才会被标记为自由

TIM截图20190801015717.png


文件描述符

由文件的内核结构可知,一个被打开的文件在系统内核中通过文件表项和v节点加以标识。有关该文件的所有后续操作,如读取、写入、随机访问,乃至关闭等,都无一例外地要依赖于文件表项和v节点。因此有必要将文件表项和v节点体现在完成这些后续操作的函数的参数中。但这又势必会将位于内核空间中的内存地址暴露给运行于用户空间中的程序代码。一旦某个用户进程出现操作失误,极有可能造成系统内核失稳,进而影响其它正常运行的用户进程。这将对操作系统的安全运行造成极大的威胁


为了解决内核对象在可访问性与安全性之间的矛盾,Unix系统通过所谓的文件描述符,将位于内核空间中的文件表项间接地提供给运行于用户空间中的程序代码
为了便于管理在系统中运行的各个进程,内核会维护一张存有各进程信息的列表,谓之进程表。系统中的每个进程在进程表中都占有一个表项。每个进程表项都包含了针对特定进程的描述信息,如进程ID、用户ID、组ID等,其中也包含了一个被称为文件描述符表的数据结构
文件描述符表的每个表项都至少包含两个数据项--文件描述符标志和文件表项指针,而所谓文件描述符,其实就是文件描述符表项在文件描述符表中从0开始的下标


通过系统调用向用户代码返回文件描述符是有意义的。一方面,用户代码在随后的文件访问中,可以文件描述符为参数,通过后续函数调用传回系统内核,内核通过查询文件描述符表,找到与该下标对应的文件描述符表项,并从中得到文件表项指针,即可借由文件表项和v节点访问文件的内容或对文件施加必要的控制。另一方面,因为用户代码所持有的仅仅是文件描述符表项在文件描述符表中的下标,而永远不可能获得文件描述符表的起始地址,因此用户代码也就无法直接访问文件表项和v节点,位于内核空间中的代码和数据得到了有效的保护

TIM截图20190801020434.png


作为文件描述符表项在文件描述符表中的下标,合法的文件描述符一定是个大于等于0的整数
每次产生新的文件描述符表项,系统总是从下标0开始在文件描述符表中寻找最小的未使用项
每关闭一个文件描述符,无论被其索引的文件表项和v节点是否被删除,与之对应的文件描述符表项一定会被标记为未使用,并在后续操作中为新的文件描述符所占用
系统内核缺省为每个进程打开三个文件描述符,它们在unistd.h头文件中被定义为三个宏
-#define STDIN FILENO0/∥标准输入
#define STDOUT FILENO 1//标准输出
#define STDERR FILENO2/∥标准错误


文件描述符修改实现I/O重定向

Shell对这样的命令行执行如下操作
a.out 0<i.txt 1>o.txt2>e.txt
关闭文件描述符0,打开i.txt,该文件即获得文件描述符0

关闭文件描述符1,打开o.txt,该文件即获得文件描述符1
关闭文件描述符2,打开e.txt,该文件即获得文件描述符2
创建子进程,执行a.out
子进程继承父进程的文件描述符表,因此a.out中所有从标准输入的读取和向标准输出及标准错误的写入,都被分别定向到i.txt、o.txt和e.txt文件中,这就是IVO重定向的原理。


文件描述符的复制

复制文件描述符。新的文件描述符是当前进程最小的 未使用的

#include <unistd.h>
int dup(int oldfd);
参数:
oldfd:指定的源描述符
返回值:
成功   返回新的文件描述符
失败 -1  errno被设置

复制文件描述符
int dup2(int oldfd, int newfd);
参数:
oldfd:指定的源描述符
newfd:指定的新的描述符。如果新的描述符原来是打开的。再使用之前,先关闭原来的。
返回值:
成功   返回新的文件描述符
失败 -1  errno被设置


代码实现输出定向


#include <t_file.h>
#include <string.h>

int main(int argc,char *argv[]){
    char *msg="this is a test\n";
    //打开文件  以写的方式,文件不存在,创建文件
    //指定新建文件的权限为0644,清空
    int flag=O_WRONLY|O_CREAT|O_TRUNC;
    mode_t mode=0644;
    int fd=open(argv[1],flag,mode);
    if(fd==-1)E_MSG("open",-1);
    //保存标准输出的文件描述符
    int s_fd=dup(1);
    //将fd描述符拷贝到标准输出
    dup2(fd,1);
    close(fd);
    write(1,msg,strlen(msg));
    //恢复原来的状态
    dup2(s_fd,1);
    close(s_fd);
    write(1,msg,strlen(msg));

    return 0;
}


运行结果:
TIM截图20190801022722.png
THE END!


版权声明:若无特殊注明,本文皆为《 8964CN 》原创,转载请保留文章出处。
本文链接:[学习笔记]达内纪录片day22 linux系统调用文件管理 http://www.8964cn.net/?post=84
正文到此结束

热门推荐

发表吐槽

你肿么看?

你还可以输入 250 / 250 个字

嘻嘻 大笑 可怜 吃惊 害羞 调皮 鄙视 示爱 大哭 开心 偷笑 嘘 奸笑 委屈 抱抱 愤怒 思考 日了狗 胜利 不高兴 阴险 乖 酷 滑稽

评论信息框

吃奶的力气提交吐槽中...


既然没有吐槽,那就赶紧抢沙发吧!