Linux系统编程——文件(1)
在学习C++的同时开启了对linux的学习(毕竟老听说学C++的人就无法避免接触linux编程)。最早接触linux是去年的3月份,开mc服的时候遇到了只有linux才能搞的东西,于是就从windows转到Ubuntu了(服务器当然是命令行版的)
期间学会了对linux的文件操作,权限管理,写个sh来调控或快捷启动mcserver,开关防火墙,换下载源(不然遇到屏蔽国外的服务商就炸了),screen,tar的用法,2个linux是如何用sftp来传东西的等等。但是这些都只是应用层面,会用不代表吃透或者会进行对应的开发。
这次便开始着重学习linux对c/c++的编程。
原帖地址: https://www.cnblogs.com/ranbom/
博主LeoRanbom
只在原帖地址的博客上发布,其他地方看到均为爬取。
如果觉得不错希望来点个赞。
系统编程概述
所有操作系统都向系统中运行的程序提供函数接口服务。 Linux系统编程指程序员使用系统调用或编程语言库函数来设计程序。而shell命令则是提供给一般用户的接口,系统调用时给开发者的接口。
写程序时,可以通过man 来查询函数(命令)的用法。(刚入坑那会有前辈开玩笑说男人是万能的hhhh)。
man可以查看函数原型和所属头文件,如果函数既是linux命令,也是系统调用,如mkdir,则需要加参数2(man 2 mkdir)。
如果是库函数,则应该为参数3(man 3 库函数名)。e.g. opendir
Linux的文件结构
- /bin用于存放普通用户可执行的命令,如ls, cp, mkdir等命令。
- /boot Linux的内核及启动系统时所需要的文件,为保证启动文件更加安全可靠,通常把该目录存放在独立的分区上。
- /dev设备文件的存储目录,如硬盘、光驱等。
- /etc用于存放系统的配置文件。(用户账号和密码)
- /home普通用户的主目录(吐槽一下有些服务商的磁盘分区,root放10g,home放25g,其他放5g,home25g转眼就用完了)
- /lib用于存放各种库文件。
- /proc改目录是一个虚拟文件系统,只有在系统运行是才存在。通过访问该目录下的文件,可以获取系统的状态信息并修改某些系统的配置信息,可以简单实用cat、strings目录来查看
- /root 超级用户root的主目录
- /sbin存放的是用于管理系统的命令
- /tmp临时文件目录
- /usr用于存放系统应用程序及相关文件,如说明文档、帮助文件等。
- /var用于存放系统中经常变化的文件,如日志文件,用户邮件等。
Linux的文件系统模型
对物理磁盘的访问都是通过设备驱动程序进行的,而对设备驱动的访问则有两种途径:一是通过设备驱动本身提供的接口,它给操作系统带来很大不稳定性,所以只有在特殊情况下才允许用户进程这样来访问物理磁盘;另一种是通过虚拟文件系统(Virtual File System,VFS)提供给上层应用程序的接口。
VFS是虚拟的(和前面的/proc文件系统一样),是只存在于内存,而不存在于磁盘之中,系统运行起来才存在的。它提供了一种机制:将不同的文件系统整合在一起,并提供统一的应用程序编程接口(API)供上层的应用程序使用。VFS的使用体现了Linux文件系统的最大的特点——支持多种不同的文件系统。
Linux不仅支持ext2,ext3,也支持windows系统的文件系统,如vfat。
每次对物理磁盘的访问最小单位为一个盘面上的一个磁道上的一个扇区,即使只需要访问1个字节的数据。
因此,文件系统是由一些了块(block)构成,每个块的大小因不同的文件系统而不同,但一旦安装之后,块的大小就固定了。通常一个块大小是一个扇区的大小,一个扇区通常为512字节。
文件分类
- 普通文件(regular file):最常见的文件类型,这种文件包含了某种形式的数据。至不过对于内核而言,文本或者二进制没什么区别。对它的解释由处理该文件的应用程序完成。
- 目录文件(directory file):目录文件就是目录,目录也有访问权限,目录文件的内射是该目录下的文件和子目录的信息。对它具有读许可权的任一进程都可以读该目录内容,不过只有内核可以写目录文件。
- 字符特殊文件(character special file):用于表示系统中字符类型的设备,比如键盘、鼠标等,这些硬件对操作系统来说只是一个文件。
- 块特殊文件(block special file):用于表示系统中块类型的设备,如硬盘、光驱等。
- FIFO:这种类型文件用于进程间的通信,也成为命名管道。
- 套接字(socket):主要用于网络通信,套接字也可以用于一台主机上的进程之间的通信。(!!!)
- 符号链接(symbolic link):指向另一个文件,是另一文件的引用。
用ls -l 文件名 来查看文件类型
文件访问权限控制
Linux就是玩权限——在探索chmod道路上遇到某个前辈说的话。
从左到右,分别是:文件属性、文件数、所有者、拥有该文件用户所属的组、文件大小、文件创建的时间、文件名。
文件属性:第一位表示文件类型,后面每3位是一组。第一组:文件所有者对文件权限。第二组:与文件所有者同组的用户对文件权限。第三组:其他用户对文件权限。
r:可读;w:可写;x:可读写。
chmod 777 test.c
777是通过计算得出的。r=4,w=2,x=1,和就为7。
在程序设计时,可以通过chmod/fchmod函数对文件访问权限进行修改,在shell下输入man 2 chmod可以查看
#include<sys/type.h>
#include<sys/stat.h>
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
chmod和fchmod区别在于,前者以文件名作为第一个参数,而后知以文件描述符作为第一个参数。
mod则有以下组合
权限更改成功返回0,失败返回-1.错误代码存于系统预定义变量errno中(具体还应查询man)
文件的输入输出
这里涉及文件的输入输出函数:creat,open,close,read,write,lseek等。对于C标准库函数fopen、fclose、fread、fwrite和fseek不作叙述。
不过对于跨平台程序,还是用C标准库函数,以方便移植。create和open等系统调用使用文件描述符来标识文件,而文件描述符是UNIX/linux特有,不方便移植。(读到这里我有点疑惑?既然如此还有必要来学习这个吗,直接去看标准库函数会不会更好?但是还是有点好奇标准库函数是怎么用系统函数实现的。)
文件的创建、打开与关闭
open函数
系统调用用来打开或创建一个文件,(不知道就man它)
man获取原型:
#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是打开或创建的含路径的文件名,flag为打开方式。
- O_RDONLY:以只读方式打开文件。
- O_WRONLY:以只写方式
- O_RDWR:可读可写。
上面三个互斥,但可以用以下进行或运算:
- O_CREAT:若不存在,则自动建立(此时会有第三个参数来表示权限
- O_ECXL: 如果O_CREAT也被设置,此指令检查文件是否存在,若不存在,创建。存在将导致打开文件出错。
- O_TRUNC: 若文件存在,并以可写方式打开,此标志会将文件长度清零,使数据丢失,但属性不变。
- O_APPEND:写入的数据会以追加的方式加入到文件后面
- O_SYNC:以同步方式打开文件,任何修改都会阻塞 ,直到物理磁盘上的数据同步以后才返回。
- O_NOFOLLOW: 如果参数pathname所指的文件为一符号连接,则会令打开文件失败。
- O_DIRECTORY:如果参数pathname所指的文件非目录,则打开文件失败。
- O_NONBLOCK或O_NEDLAY:以非阻塞的方式打开文件,对于open和随后对该文件的操作,都会立即返回。
当且仅当第二参数使用了O_CREAT时,需要使用第三个参数mode。新文件的实际存取全是是mode与umask安装(mode & ~umask)运算以后的结果。若umask为045,mode为0740,则实际存取权限为0700,即-rwx------。
mode参数与chmod函数相同。
成功调用open函数,会返回一个文件描述符,若有错误发生则返回-1,并把错误代码赋给errno。
creat函数
文件的创建可以通过creat系统调用来完成。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int creat(const char *pathname, mode_t mode);
其中第一个参数pathname是要打开或创建的文件名,如果pathname指向的文件不存在,则创建一个新文件;如果pathname指向的文件存在,则原文件被覆盖。第二个参数mode与open函数系统。所以creat相当于变相的使用open函数:open(const char * pathname,(O_CREAT|O_WRONLY|O_TRUNC));
成功调用creat会返回一个文件描述符,若有错误发生则返回-1。并把错误代码赋给errno。
注意:creat只能以只写的方式打开创建的文件,creat无法创建设备文件,设备文件的创建要使用mknnod函数
close函数
close系统调用用来关闭一个已经打开的文件。
#include<unistd.h>
int close(int fd);
close只有一个参数,表示需要关闭的文件的文件描述符。由open或creat函数得到。当close调用成功是,返回值为0,发生错误时返回-1,并设置错误代码。
close调用成功时并不保证数据能全部写回硬盘。
程序也可以不调用close,因为在进程结束时,内核会自动关闭所有已打开的文件。但是这不是一个很好的习惯,还是建议显式调用close函数。
文件的读写
read()函数
系统调用用来从打开的文件中读取数据。
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count);
从文件描述符fd所指向的文件中读取count个字节的数据到buf所指向的缓存中。若count为0,则read()不会读取数据,只返回0.返回值表示实际读取到的字节移动。如果read()顺利返回实际读到的字节数,建议将返回值与count作比较。如果少于count,则可能读到了文件尾或者read()被信号中断了读取过程,or anything else。
有错误发生时,返回-1,错误代码存errno。
write()函数
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t count);
将buf所指向的缓冲区中的count个字节数据写入到由文件描述符fd所指示的文件中。文件读写指针会随之移动。调用成功,返回写入的字节数。发生错误返回-1,错误代码存errno。
文件读写指针的移动
lseek系统调用用来移动文件读写指针的位置。
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
每一已打开的文件都有一个读写位置,当打开文件是通常读写位置是指向文件开头,若是以添加的方式打开文件(调用open函数时使用了O_APPEND),则读写位置会指向文件尾。当调用read或write时,读写位置会随之增加,lseek()便用来控制该文件的读写位置。参数fildes为已打开的文件面舒服,参数offset为根据参数whence来移动读写位置的位移数。参数whence有以下三种取值:
- SEEK_SET 从文件开始出计算偏移量,文件指针到文件开始出的距离为offset。
- SEEK_CUR 从文件指针的当前位置开始计算偏移量,文件指针值等于当前指针值加上offset的值,offset值允许取负数。
- SEEK_END 从文件结尾开始计算,文件指针值等于当前指针值加上offset的值,offset允许取负值。
lseek允许文件指针值设置到EOF之后,但这样做不会改变文件的大小。如果使用write对EOF之后的位置写入了数据,之前的EOF处与后面写入的数据之间将会存在一个间隔,此时,使用read读取这个间隔的数据后,将返回0.
调用成功返回当前的读写位置,也就是距离文件开始处多少字节,错误返回-1,错误代码存errno。
lseek常用方法:
- 将文件读写指针移动到文件开头lseek(int fildes,0,SEEK_SET);
- 将文件读写指针移动到文件结尾lseek(int fildes,0,SEEK_END);
- 获取文件读写指针当前位置lseek(int fildes,0,SEEK_CUR)
注:有些设备(文件)不能使用lseek。Linux系统不允许lseek()对tty设备进行操作,会返回错误代码ESPIPE