设备和文件IO
1、认识Linux文件
在Linux下“一切皆是文件”!不仅普通的文件,目录、字符设备、块设备、套接字等在unix/linux中都是以文件被对待;他们虽然类型不同,但是对其提供的却是同一套操作界面。
常见的几种Linux下面的文件:
普通文件
目录
设备
套接字
linux中的设备有2种类型:字符设备(无缓存且只能顺序存取)、块设备(有缓存且可以随机存取)。每个字符设备和块设备都必须有主、次设备号,主设备号相同的设备是同类设备(同一个驱动程序)。这些设备中,有些设备是对实际存在的物理硬件的抽象,而有些设备则是内核自身提供的功能(不依赖于特定的物理硬件,又称“虚拟设备”)。每个设备在 /dev 目录下都有一个对应的文件(节点)。
cat /proc/devices 查看所有的设备,分为字符设备和块设备显示
2、什么是设备文件
设备文件是用来代表物理设备的。多数物理设备是用来进行输出或输入的,所以必须由某种机制使得内核中的设备驱动从进程中得到输出送给设备。这可以通过打开输出设备文件并且写入做到,就象写入一个普通文件。
在Linux系统下,设备文件是种特殊的文件类型,其存在的主要意义是沟通用户空间程序和内核空间驱动程序。换句话说,用户空间的应用程序要想使用驱动程序提供的服务,需要经过设备文件来达成。Linux系统所有的设备文件都位于**/dev**目录下。
ls /dev -l
stat /dev/tty 查看索引节点
Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:
系统找到这个文件名对应的inode号码
通过inode号码,获取inode信息
根据inode信息,找到文件数据所在的block,读出数据。
ls -i 命令可以查看文件名对应的inode号码
设备的工作原理
Linux设备操作
3、系统调用
系统调用是操作系统提供给用户的一组“特殊”接口
系统调用并非直接和程序员或系统管理员直接打交道,而是通过软中断的方式向内核提交请求,从而获取内核函数的服务入口(系统调用表)
系统调用让系统从用户空间进入内核空间内运行,运行后将结果返回给应用程序(内核态->用户空间)
函数库调用 与 系统调用
4、C库的文件操作
文件系统调用
open系统调用
read系统调用
write系统调用
create系统调用
close系统调用
mkdir系统调用
5、文件描述符fd
每个进程PCB结构中有文件描述符指针,指向files_struct的文件描述符表,记录每个进程打开的文件列表
系统内核不允许应用程序访问进程的文件描述符表,只返回这些结构的索引即文件描述符ID(File Description)给应用程序
Linux系统中,应用程序通过这些文件描述符来实现让内核对文件的访问
每个进程能够访问的文件描述符是有限制的,通过#ulimit –n可以查看,默认是1024
特殊文件描述符号:
标准输入STDIN_FILENO
标准输出STDOUT_FILENO
标准错误STDERR_FILENO
每个进程被加载后,默认打开0,1,2这三个文件描述符
6、函数介绍和使用
open系统调用
有几种方法可以获得允许访问文件的文件描述符。最常用的是使用open()(打开)系统调用
函数原型:
int open(const char *path, int flags);
int open(const char *path, int flags, mode_t mode);
参数:
path:文件的名称,可以包含(绝对和相对)路径
lags:文件打开模式
mode:用来规定对该文件的所有者,文件的用户组及系统中其他用户的访问权限
返回值:
打开成功,返回文件描述符;
打开失败,返回-1
打开文件的方式:
所有这些标志值的符号名称可以通过#include<fcntl.h>访问
访问权限:
文件打开示例:
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int outfd = 0;
outfd = open("myfile",O_WRONLY | O_CREAT | O_TRUNC,S_IRWXU | S_IRGRP);
if(outfd == -1){
perror("fail to open file\n");
exit(-1);
}else{
perror("success to open file\n");
}
close(outfd);
return 0;
}
close系统调用
将进程中的fd对应的文件描述符表结构释放
函数原型:
int close(int fd);
函数参数:
fd :要关闭的文件的文件描述符
返回值
如果出现错误,返回-1
调用成功返回0
read系统调用
一旦有了与一个打开文件描述相连的文件描述符,只要该文件是用O_RDONLY或O_RDWR标志打开的,就可以用read()系统调用从该文件中读取字节
函数原型:
int read(int fd, void *buf, size_t nbytes);
参数
fd:想要读的文件的文件描述符
buf: 指向内存块的指针,从文件中读取来的字节放到这个内存块中
nbytes: 从该文件复制到buf中的字节个数
返回值
如果出现错误,返回 -1
返回从该文件复制到规定的缓冲区中的字节数
write系统调用
用write()系统调用将数据写到一个文件中
函数原型:
int write(int fd,void *buf,size_t nbytes);
函数参数:
fd :要写入的文件的文件描述符
buf: 指向内存块的指针,从这个内存块中读取数据写入到文件中
nbytes: 要写入文件的字节个数
返回值
如果出现错误,返回 -1
如果写入成功,则返回写入到文件中的字节个数
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int fd = 0,size = 0;
char buf[] = "Hello world!";
fd = open("first",O_WRONLY | O_TRUNC | O_CREAT,S_IRWXU);
if(fd > 0){
size = write(fd,buf,sizeof(buf));
if(size > 0){
printf("write data to file success\n");
}
close(fd);
}
return 0;
}
7、文件的随机读写
到目前为止的所有文件访问都是顺序访问。这是因为所有的读和写都从当前文件的偏移位置开始,然后文件偏移值自动地增加到刚好超出读或写结束时的位置,使它为下一次访问作好准备。
有个文件偏移这样的机制,在Linux系统中,随机访问就变得很简单,你所需做的只是将当前文件移值改变到有关的位置,它将迫使一次read()或write()发生在这一位置。(除非文件被O_APPEND打开,在这种情况下,任何write调用仍将发生在文件结束处)
8、lseek系统调用
功能说明:通过指定相对于开始位置、当前位置或末尾位置的字节数来重定位 curp,这取决于 lseek() 函数中指定的位置
原型:
off_t lseek(int fd,off_t offset,int whence);
参数
fd:文件描述符
offset:偏移量
whence:搜索的起始位置
返回值
返回新的文件偏移值
9、chmod和fchmod系统调用
功能说明:用来改变给定路径名pathname的文件的权限位
原型:
int chmod(char *path,mode_t mode);
int fchmod(int fd,mode_t mode);
返回值:调用成功返回0,失败返回-1
10、chown和fchown系统调用
功能说明:用来改变文件所有者的识别号(owner id)或者它的用户组识别号(group ID)
原型:
int chown(char *path, uid_t owner,gid_t group);
int fchown(int fd, uid_t owner,gid_t group);
参数
owner:所有者识别号
group:用户组识别号
11、mkdir系统调用
功能说明:用来创建一个称为pathname的新目录,它的权限位设置为mode
原型:
int mkdir(char *pathname,mode_t mode);
返回值:调用成功返回0,失败返回-1
12、rmdir系统调用
功能说明:删除一个空目录
原型:
int rmdir(char *pathname);
返回值:调用成功返回0,失败返回-1
13、目录访问
(1)opendir
功能说明:打开一个目录
原型:
DIR* opendir(char *pathname);
返回值
打开成功,返回一个目录指针
打开失败,则返回 0
(2)readdir
功能说明:访问指定目录中下一个连接的细节
原型:
struct dirent* readdir(DIR *dirptr);
返回值:
返回一个指向dirent结构的指针,它包含指定目录中下一个连接的细节;
没有更多连接时,返回NULL
struct dirent
{
long d_ino; /* 目录i结点编号 */
off_t d_off; /* 目录文件开关至此目录进入点的位移 */
unsigned short d_reclen; /* d_name的长度 */
char d_name [NAME_MAX+1]; /* 以NULL结尾的文件名 */
}
如果调用opendir打开某个目录之后,第一次调用readdir函数,则返回的是该目录下第一个文件的信息,第二次调用readdir函数返回该目录下第二个文件的信息,依此类推。如果该目录下已经没有文件信息可供读取,则返回NULL。
(3)closedir
功能说明:关闭一个已经打开的目录
原型:
int closedir (DIR *dirptr);
(4)示例:目录操作
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
int my_read_dir(const char *path){
DIR * dir;
struct dirent *ptr;
if((dir = opendir(path)) == NULL){
return -1;
}
while((ptr = readdir(dir)) != NULL){
printf("file name: %s\n",ptr->d_name);
}
closedir(dir);
return 0;
}
int main(int argc,char *argv[]){
if(my_read_dir(argv[1]) < 0 ){
exit(1);
}
return 0;
}
14、文件记录锁 - fcntl函数
什么是文件锁:当有多个进程同时对某一文件进行操作时,就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。
Linux中文件记录锁可以对文件某一区域进行文件记录锁的控制。它是通过fcntl函数来实现的。
fcntl函数
功能说明:管理文件记录锁的操作
原型:
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd,int cmd,struct flck *lock);
参数:
fd:文件描述符
cmd:功能符号
F_SETLK:用来设置或释放锁
F_GETLK:用来获得锁信息
lock:存储锁信息的结构体指针
返回值
调用成功返回 0,失败返回 -1
//锁信息结构体
struct flock
{
short l_type; //锁的类型
short l_whence; //偏移量的起始位置
off_t l_start; //从l_whence的偏移量
off_t l_len; //从l_start开始的字节数
pid_t l_pid; //锁所属进程ID(一般不用)
}
l_type:
F_RDLCK读锁、F_WRLCK写锁、F_UNLCK空锁
l_whence:
SEEK_SET起始、SEEK_CUR当前、SEEK_END末尾
l_len:
为0时表示从起点开始直至最大可能位置为止
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(){
int fd;
struct flock lock;
fd = open("example",O_CREAT | O_TRUNC | O_RDWR,S_IRWXU);
if(fd == -1){
printf("open file error\n");
}
memset(&lock,0,sizeof(struct flock));
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
if(fcntl(fd,F_GETLK,&lock) == 0){
if(lock.l_type != F_UNLCK){//如果有锁,不能上锁
printf("lock can not by set in fd\n");
}else{//上锁
lock.l_type = F_WRLCK;
if(fcntl(fd,F_SETLK,&lock) == 0){
printf("set write lock success!\n");
}else{
printf("set write lock fail!\n");
}
getchar();
//解锁
lock.l_type = F_UNLCK;
fcntl(fd,F_SETLK,&lock);
}
}
close(fd);
return 0;
}