Linux服务器开发学习之系统编程
文章目录
1. 文件I/O
1.1 文件描述符
对于Linux内核而言,所有打开的文件都是通过文件描述符来操作的。文件描述符是一个非负整数。当创建或者打开一个文件时,内核向进程返回一个文件描述符。在一个进程中,通常会默认打开3个文件描述符——0,1,2。0表示标准输入(键盘);1表示标准输出(显示器);2表示标准出错(显示器)。在程序中,用STDIN_FILENO替代0;用STDOUT_FILENO替代1;用STDERR_FILENO替代2。
1.2 open函数
调用open函数可以打开或创建一个文件。open函数的原型为:
#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是指以何种方式打开文件。
flags参数的取值及作用如下:O_RDONLY、O_WRONLY、O_RDWR三者只能指定一个,其他的可用 |(或运算)进行组合。
- O_RDONLY:以只读方式打开文件
- O_WRONLY:以只写方式打开文件
- O_RDWR:以读写方式打开文件
- O_APPEND:每次写时都追加到文件末尾
- O_CREAT:若文件不存在,则创建该文件,使用此选项时,需要第三个参数mode,指定新文件的访问权限。
- O_EXCL:如果同时指定了O_CREAT,而文件已经存在,则会出错。
- O_TRUNC:如果此文件已经存在,而且为只写或读写形式打开时,将文件的长度截短为0。
- O_NONBLOCK:非阻塞模式
mode参数是指定文件的访问权限的。mode的形式为0xyz。x、y、z的取值为0~7. - x表示文件所有者的权限
- y表示文件所属组的权限
- z表示其他人的权限
x、y、z的具体取值可以为 - 4:可读
- 2:可写
- 1:可执行
- 4、2、1的组合
文件最终的访问权限并不是我们给出的mode的值,最终的mode的值为
mode=mode&(~umask)
umask是当前文件所属进程的掩码。
当open操作执行成功时,返回文件描述符;失败时返回-1。
open示例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
int fd=open("./hello.txt",O_RDWR|O_CREAT|O_EXCL,0777);
if(-1 == fd) //返回-1表示文件已经存在
{
printf("文件打开失败!\n");
perror("open");
exit(-1);
}
printf("文件打开成功!\n");
return 0;
}
运行结果:
文件hello.txt不存在时,open执行成功,在当前目录创建了hello.txt文件。
当文件hello.txt存在时,open执行失败,打印失败原因:文件已存在。
1.3 lseek函数
每个打开的文件都有一个与之相关联的“当前文件偏移量”。它通常是一个非负整数,用来度量从文件开始处计算的字节数。通常文件的读、写操作都是从当前文件偏移处开始,并使偏移量增加所读写的字节数。当打开一个文件时,除非使用O_APPEND标志,否则该偏移量总是为0。
可以调用lseek显式地为一个打开的文件设置其偏移量。lseek函数的原型为:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
对参数offset的解释与whence的取值有关:
- 若whence是SEEK_SET,则将文件的偏移量设置为距文件开始处offset个字节,offset大于等于0。
- 若whence是SEEK_CUR,则将文件偏移量设置为当前值加offset,offset可正可负。
- 若whence是SEEK_END,则将文件偏移量设置为文件长度加offset,offset可正可负。
若lseek执行成功,则返回新的文件偏移量。
可以用以下方式确定所打开文件的当前偏移量:
off_t currpos;
currpos=lseek(fd,0,SEEK_CUR);
可以用以下方式得到文件的大小(单位为字节)
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
off_t filesize;
int fd=open("./hello.txt",O_RDWR);
if(-1 == fd)
{
printf("文件打开失败!\n");
perror("open");
exit(-1);
}
printf("文件打开成功!\n");
filesize=lseek(fd,0,SEEK_END);
printf("文件的大小为:%d\n",filesize);
return 0;
}
文件的偏移量可以大于文件当前的长度,在这种情况下,对该文件的下一次写操作将加长文件,并在文件中构成一个空洞,这一点是允许的,位于文件中但没写过的字节都被读为0。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
off_t filesize;
int fd=open("./hello.txt",O_RDWR);
if(-1 == fd)
{
printf("文件打开失败!\n");
perror("open");
exit(-1);
}
printf("文件打开成功!\n");
filesize=lseek(fd,100,SEEK_END);//拓展文件大小
write(fd,"a",1);
close(fd);
return 0;
}
文件长度变为13+100+1=114
文件空洞以^@填充:
1.3 read函数
调用read函数从打开的文件中一次读取count个字节的数据,并存到buf指向的内存空间。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
若read执行成功,则返回读到的字节数,如果已经到达文件末尾,则返回0。
示例代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
char buf[1024]={0};
ssize_t cnt;
int fd=open("./hello.txt",O_RDWR);
if(-1 == fd)
{
printf("文件打开失败!\n");
perror("open");
exit(-1);
}
printf("文件打开成功!\n");
cnt=read(fd,buf,sizeof(buf)/sizeof(char));
printf("读取到%ld个字符\n",cnt);
printf("读取到的内容为:%s",buf);
close(fd);
return 0;
}
1.4 write函数
调用write函数将buf指向的内存空间的count字节的数据写入到打开的文件中。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
示例代码
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main(int argc,char * argv[])
{
char buf[1024]="The best programming language is C, and no rebuttal is accepted.";
ssize_t cnt;
int fd=open("./hello.txt",O_RDWR);
if(-1 == fd)
{
printf("文件打开失败!\n");
perror("open");
exit(-1);
}
printf("文件打开成功!\n");
cnt=write(fd,buf,strlen(buf));
printf("写入文件的字符数为%ld\n",cnt);
close(fd);
return 0;
}
1.5 close函数
可以调用close函数来关闭一个已打开的文件。
#include <unistd.h>
int close(int fd);
1.6 内核中用于所有IO的有关文件的数据结构
内核使用三种数据结构表示打开的文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。
- 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件的文件描述符表,每个描述符占一项。与每个文件描述符相关联的是:
- 文件描述符标志
- 指向一个文件表项的指针
- 内核为所有打开文件维持一张文件表,每个文件表项包括:
- 文件状态标志
- 当前文件偏移量
- 指向该文件v节点表项的指针
- 每个打开的文件都有一个v节点结构。v节点包括了文件类型和对此文件进行各种操作的函数的指针。对于大多数文件,v节点还包括了该文件的i节点。i节点包含了文件的所有者、文件长度、文件所在的设备、指向文件实际数据块在磁盘上所在位置的指针等等。
如果两个独立进程各自打开了同一个文件,则有
现在可以对前面的函数操作进行进一步说明: - 在完成每一个write后,在文件表项中的当前文件偏移量即增加所写的字节数,如果写操作使得当前文件的偏移量超过了当前文件的长度,则在i节点表项中的当前文件长度被设置为当前文件偏移量。(也就是加长了文件)
- 如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有O_APPEND标志的文件执行写操作时,在文件表项中的当前文件偏移量首先被设置为i节点表项中的文件长度。这就使得每次写的数据都添加到文件的末尾。
- 若一个文件用lseek定位到文件的尾端,则文件表项中的当前文件偏移量被设置为i节点表项中的当前文件长度。
可能有多个文件描述符项指向同一个文件表项。
1.7 dup和dup2函数
dup和dup2函数都可用来复制一个现存的文件描述符
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
由dup返回的新文件描述符一定是当前可用文件描述符中的最小值。用dup2则可以由newfd指定新描述符的数值。如果newfd对应的文件已经打开,则先将其关闭。如果fd等于newfd,则dup2返回newfd,而不关闭它。
这些函数返回的新文件描述符与参数fd共享同一个文件表项。
1.8 fcntl函数
fcntl函数可以改变已打开的文件的性质
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函数有5种功能:
- 复制一个现有的文件描述符 cmd=F_DUPFD
- 获得/设置文件描述符标记 cmd=F_GETFD/F_SETFD
- 获得/设置文件状态标志 cmd=F_GETFL/F_SETFL
- 获得/设置在异步I/O所有权 cmd=F_GETOWN/F_SETOWN
- 获得/设置在记录锁 cmd=F_GETLK/F_SETLK、F_SETLKW
F_DUPFD:复制文件描述符fd,新文件描述符作为函数值返回。它是尚未打开的文件描述符中大于或等于第三个参数值中各值的最小值。新文件描述符与fd共享同一个文件表项。
F_GETFD:对应于fd的文件描述符标志作为函数值返回。
F_SETFD:对fd对应的文件重新设置文件描述符,新文件描述符按第三个参数设置。
F_GETFL:对应于fd的文件状态标志作为函数值返回。在说明open函数时,已说明了文件状态标志。
F_SETFL:将文件状态标志设置为第三个参数的值。
F_GETOWN:取当前接收SIGIO和SIGURG信号的进程ID或进程组ID
F_SETOWN:设置接收SIGIO和SIGURG信号的进程ID或进程组ID
1.9 ioctl函数
ioctl函数是I/O操作的杂物箱,不能用上面介绍的函数进行的I/O操作通常都能用ioctl表示。
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
2 文件和目录
2.1 stat、fstat和lstat函数
#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);
pathname是要获取的文件属性的文件名。fd为已打开文件的文件描述符。当文件名是一个符号链接时,要获取该符号链接的有关信息,需要用lstat函数。如果用stat获取符号链接文件的有关信息,则最终会得到该符号链接指向的文件的信息。
statbuf是一个指针,它指向一个我们必须提供的结构。这些函数将自动填写由statbuf指向的内存。struct stat的基本形式是
struct stat
{
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode;//文件的类型和存取的权限
nlink_t st_nlink;//链接到该文件的硬连接数目
uid_t st_uid;//用户ID
gid_t st_gid;//组ID
dev_t st_rdev;//设备类型,若此文件为设备文件,则为其设备编号
off_t st_size;//文件字节数(文件大小)
blksize_t st_blksize;//块大小
blkcnt_t st_blocks;//块数
time_t st_atime;//最后一次访问时间
time_t st_mtime;//最后一次修改时间
time_t st_ctime;//最后一次改变时间(指属性)
}
该结构中的每一个成员都是系统的基本数据类型。
2.2 文件类型
文件类型包括如下的几种:
- 普通文件。这是最常用的数据类型。
- 目录文件。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。对一个目录具有读权限的任一进程都可以读该目录的内容,但只有内核可以直接写目录文件。
- 块设备文件。这种文件类型提供对块设备(例如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
- 字符设备文件。这种文件类型提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备文件要么是字符设备文件,要么是块设备文件。
- FIFO。这种类型文件用于进程间通信,有时也称其为命名管道。
- 套接字。这种类型文件常用于进程间的网络通信。
- 符号链接文件。这种文件类型指向另一个文件。
文件类型信息包含在stat结构中的st_mode成员中,可以用下面的宏确定文件类型
宏 | 文件类型 |
---|---|
S_ISREG () | 普通文件 |
S_ISDIR () | 目录文件 |
S_ISCHR() | 字符设备文件 |
S_ISBLK() | 块设备文件 |
S_ISFIFO() | 管道文件 |
S_ISLNK() | 符号链接 |
S_ISSOCK() | 套接字 |
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main(int argc,char* argv[])
{
int ret;
struct stat buf;
if(stat("./hello.txt",&buf)<0)
{
printf("获取文件信息失败!\n");
perror("stat");
exit(-1);
}
else
{
printf("获取文件信息成功!\n");
if(S_ISREG(buf.st_mode))
printf("是一个普通文件!\n");
}
return 0;
}
2.3 文件访问权限
st_mode的值也包含了针对文件的访问权限位。每个文件有9个访问权限位,可以将它们分成三类:
st_mode屏蔽 | 意义 |
---|---|
S_IRUSR | 所有者-读 |
S_IWUSR | 所有者-写 |
S_IXUSR | 所有者-执行 |
S_IRGRP | 所属组-读 |
S_IWGRP | 所属组-写 |
S_IXGRP | 所属组-执行 |
S_IROTH | 其他人-读 |
S_IWOTH | 其他人-写 |
S_IXOTH | 其他人-执行 |