作为一名C++后台开发程序员,在这个领域需要不断的学习进步,才能够稳定前行。对于毕业工作了一年的我,在着期间,对于Linux系统可以说是很少接触,因为现在的项目主要是windows项目。但是,个人认为,作为后台开发工程师,Linux系统的学习是必须的,最起码要熟悉Linux系统。而对于几乎没接触过Linux系统的我来说,学习Linux系统要从零开始。但是这些不重要,重要是的肯学。所以利用闲余时间自学Linux系统。当然,最基础的就是一边看书一边动手实践学习。但是很多时候,看书时可以明白理解,但是过了一段时间,如果没有使用到看书所学的知识,很快就会给遗忘掉。为此,我通过写博客的来记录我看书过程中学到的知识总结,以便之后若忘记可以翻看自己的博客来回忆所学知识,同时也希望总结出来的这些Linux系统知识能够帮助到有需要的人。当然,写博客不仅仅是这个目的。。好了,废话不多说(毕竟本人第一次写博客,有点啰嗦)。开始记录我学习Linux的路程。
今天主要是学习了Linux的文件相关的系统I/O函数。这些I/O函数包括了open、create、read、write、lseek、close、dup、dup2等函数。这些文件操作函数,相比一般C++程序员都会了解,C++标准库都有自带的标准I/O操作,但这里记录的是Linux系统自带的I/O操作函数。
在学习这些函数之前,需要先了解Linux系统的进程间的文件共享和管理的问题。系统内核为了维持进程与文件之间的关系,定义了三个结构体。
1、进程表:内核维护一张进程表,记录每一个进程项,进程项中维持了一张在该进程中所有的打开的文件描述符,每个文件描述符记录了问价描述符标志和文件表项。
2、文件表:内核维护一张文件表,用来记录所有的打开的文件表项。每个文件表项记录文件描述符状态标记、文件指针当前偏移量以及指向文件v节点所在地址的指针。
3、v节点表:内核位置一张v节点表,记录所有打开的v节点,v节包含v节点信息以及i节点信息,每一个打开的文件有一个v节点结构,v节点中包含的i节点(索引节点)记录了文件的详细信息,例如记录了文件的长度,在盘中的地址等具体信息。
进程之间对文件的管理就是通过这个三张表的关系层层连接。一个进程打开一个未打开的文件时候,就会生成产生文件描述符、文件表项一v节点结构,来记录这个文件的详细信息,如若另外一个进程打开与该进程相同的文件时候,则在另外一个进程中只会生成一个当前进程下的文件描述符和一个文件表项,但是是不会在生成v节点结构,一个打开的文件有且只有一个v节点结构,也就是两个进程的文件表项就会共用该v节点。最后给出一张图,帮助理解。如下图所示(打开文件的内核将数据结构):
说完上面文件知识之后,下面就是I/O系统函数:
open与create函数:open函数与create函数都可以打开一个文件,区别是create只能创建打开一个只写的文件,open函数可以打开或者创建一个可读、可写或者可读写的文件,功能更为多样。函数成功均返回一个文件描述符,否则返回-1.
//@param pathname 需要打开或者创建的文件的路径名
//@param flag 文件状态标志(这里列出常用的几个状态标志)
//O_WRONLY/O_RDONLY/RDWR 分别为可写、可读和可写可读标志,三者不能共存,只能有其中一个
//O_APPEND 为次写文件时候都会追加到文件末尾,不设置则最近一次写入文件的内容会覆盖之前的内容;
//O_CREAT 若果文件不存在就创建该文件,若果设置了该状态标志的,就要使用第三个参数mode指定创建的文件相关权限
//O_EXCL 如果同时指定该标志和O_CREAT标志,则如果指定打开的文件存在则函数返回打开时失败。
//O_TRUNC 若果文件存在,而且指定只读或者只写成功打开,则会将制定文件长度截断为零。
//O_NONBLOCK 设置文件描述符为非阻塞。
//O_NOCTTY 如果pathname指的是终端设备,则不将设备作为此进程的控制终端。
//O_SYNC 每次写操作都要阻塞等待物理I/O操作完成后才返回
int open(const char *pathname, int flag);
int open(const char *pathname, int flag, mode_t mode);
int creat(const char *pathname, mode_t mode);//在这里不管设置mode为可读或者可写或者可读写,只要该函数返回成功就只会是可写文件描述符
close函数:虽然说进程结束后,会自动关闭所有打开的文件描述符,但是还是希望程序员自己手动关闭。函数成功返回0,失败返回-1。
//@param fd 需要关闭文件的文件描述符
int close(int fd);
lseek函数:lseek函数的调用指挥导致文件指针的偏移,而不会导致I/O操作。函数成功返回文件新的位移,失败返回-1.
//@param fd 指定文件的文件描述符
//@param offset 一般为非负数(有的Linux系统支持负数,但是很少), 需要文件指针偏移的偏移量大小
//@param whence 偏移方式,值有SEEK_SET/SEEK_CUR/SEEK_END
1、SEEK_SET 将文件指针移动到距离文件开始位置为offset的地方
2、SEEK_CUR 将文件指针从当前位置开始移动offset
3、SEEK_END 将文件指针从文件结尾开始移动offset,这也是一种扩展文件大小的方式,但是很少用,被扩展的区间全被用\0字符填充,可以使用命令od -c + 文件名 查看详情
lseek(int fd, off_t offset, int whence);
read函数和write函数:分别为读取文件内容和写入文件的函数。函数特点类似,函数读取或者写入成功都会返回一个数值,标识成功读取或者写入的字节数,如果失败均返回-1。
//@param fd 需要操作的文件的文件描述符
//@param buf 用来接收读取数据的换冲区或者用来写入数据的换冲区
//@param count 换冲区的大小
size_t read(int fd, void *buf, size_t count);
size_t write(int fd, void *buf, size_t count);
接下来,就是dup函数和dup2函数。这两函数作用本质上的作用是一样,就是复制一个文件描述符。两个函数成功时候都返回一个文件描述符,失败返回-1。但是dup2可以说dup函数的加强版。调用dup函数成功时候,返回的是当前进程下未打开的文件描述符的最小文件描述符。但是dup2函数则可以指定一个文件描述符作为目的文件描述符,成功后就返回指定的文件描述符。dup2函数在底层的实现是如果目的文件描述符已经打开,则先将其关闭,然后再对该文件描述符赋予新值。最重要的一点是,要区分清楚,使用这两个函数复制返回的文件描述符指向同一个文件表项,而不是像进程间打开同意文件,两个进程间各自产生一个文件表项。
//@param oldfd 需要复制的文件描述符
//@param newfd 将需要复制的文件描述符复制到的指定文件描述符
int dup(int oldfd);
int dup2(int oldfd, int newfd);
好了,今天先写这么多,一点一点记录学习过程,希望能够帮助自己,也能够帮助他人。