第二章
设备和文件IO
本章目标
• 理解设备和文件的关系
• 理解系统调用和系统API等关系,掌握系统
调用的工作过程
• 掌握系统调用open/read/write/fcntl等使用
Linux中的设备管理
• Linux设备管理概述
在Linux系统中,用户是通过文件系统与设
备接口的。所有设备都作为特别文件,从
而在设备管理上具有下列特性。
– (1)每个设备都对应文件系统中的一个索
引节点,都有一个文件名。
–(2)应用程序通常可以通过系统调用open()
打开设备文件,建立起与目标设备的连接。
–(3)对设备的使用类似于对文件的存取。
–(4)设备驱动程序都是系统内核的一部分,它
们必须为系统内核或者它们的子系统提供一个标
准的接口。
–(5)设备驱动程序使用一些标准的内核服务,
如内存分配等。
设备工作原理
设备分类
• 按设备属主关系
–系统设备
–用户设备
• 按设备信息交换单位来分
–字符设备
–块设备
• 按设备共享属性来分
–独享设备
–共享设备
Linux设备操作
• 设备或文件操作两种方式:
–用户编程接口 API
–系统调用
系统调用
• 系统调用是操作系统提供给用户的一组“特
殊”接口
• 系统调用并非直接和程序员或系统管理员直
接打交道,而是通过软中断的方式向内核提
交请求,从而获取内核函数的服务入口(系
统调用表)
• 系统调用让系统从用户空间进入内核空间内
运行,运行后将结果返回给应用程序(内核
态->用户空间)
系统调用和系统API等区别
• 系统API
–主要是通过C库libc来实现,程序员多采用这种
方式与内核交互,这些API通过系统调用来实现
• 系统命令
–系统管理员采用系统命令与内核交互,是一个
可执行文件,通过系统API及系统调用来实现
• 外壳程序
–一系列系统命令和SHELL脚本共同组合成的程
序。
C库的文件操作
函数名 功能
fopen( ) 打开文件
fclose( ) 关闭文件
fputc( ) 将字符写入文件中
fgetc( ) 从文件中读取字符
fread() 将数据从文件中读到缓冲区
fwrite() 将数据从缓冲区写入文件
fseek( ) 在文件中搜索指定位置
fprintf( ) 操作类似于 printf(),但是用于文件
fscanf( ) 操作类似于 scanf(),但是用于文件
feof( ) 如果到达文件结尾,返回 true
ferror( ) 如果出错,返回 true
rewind( ) 将文件位置指示器重新置于文件开头
remove( ) 删除文件
fflush( ) 将内部缓冲区的数据写入指定文件
文件系统调用
• open系统调用
• read系统调用
• write系统调用
• create系统调用
• close系统调用
• mkdir系统调用
• …
文件描述符fd
• 每个进程PCB结构中有文件描述符指针,指
向files_struct的文件描述符表,记录每个进
程打开的文件列表
• 系统内核不允许应用程序访问进程的文件描
述符表,只返回这些结构的索引即文件描述
符ID(File Description)给应用程序
• Linux系统中,应用程序通过这些文件描述
符来实现让内核对文件的访问
• 每个进程能够访问的文件描述符是有限制的,
通过#ulimit –n可以查看
特殊文件描述符号
• 标准输入STDIN_FILENO
• 标准输出STDOUT_FILENO
• 标准错误STDERR_FILENO
每个进程被加载后,默认打开0,1,2这三个文件描述符
open系统调用2-1
• 有几种方法可以获得允许访问文件的文件描述符。
最常用的是使用open()(打开)系统调用
–函数原型
int open(const char *path, int flags);
– 参数
path :文件的名称,可以包含(绝对和相对)路径
flags:文件打开模式
– 返回值
打开成功,返回文件描述符;
打开失败,返回-1
open系统调用2-2
–函数原型
int open(const char *path, int flags,mode_t mode);
– 参数
path :文件的名称,可以包含(绝对和相对)路径
flags:文件打开模式
mode: 用来规定对该文件的所有者,文件的用户组及系统中其他
用户的访问权限,则文件权限为:mode&(~umask)
– 返回值
打开成功,返回文件描述符;
打开失败,返回-1
打开文件的方式
打开方式 描述
O_RDONLY 打开一个供读取的文件
O_WRONLY 打开一个供写入的文件
O_RDWR 打开一个可供读写的文件
O_APPEND 写入的所有数据将被追加到文件的末尾
O_CREAT 打开文件,如果文件不存在则建立文件
O_EXCL 如果已经置O_CREAT且文件存在,则强制open()
失败
O_TRUNC 在open()时,将文件的内容清空
所有这些标志值的符号名称可以通过#include<>访问
访问权限
打开方式 描述
S_IRUSR 文件所有者的读权限位
S_IWUSR 文件所有者的写权限位
S_IXUSR 文件所有者的执行权限位
S_IRWXU S_IRUSR|S_IWUSR|S_IXUSR
S_IRGRP 文件用户组的读权限位
S_IWGRP 文件用户组的写权限位
S_IXGRP 文件用户组的执行权限位
S_IRWXG S_IRGRP|S_IWGRP|S_IXGRP
S_IROTH 文件其他用户的读权限位
S_IWOTH 文件其他用户的写权限位
S_IXOTH 文件其他用户的执行权限位
S_IRWXO S_IROTH|S_IWOTH|S_IXOTH
文件打开示例
#include <>
#include <sys/>
#include <>
void 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); //关闭文件描述符
}
关闭文件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
返回从该文件复制到规定的缓冲区中的字节数,文件结束,返回0否则
write系统调用
• 用write()系统调用将数据写到一个文件中
函数原型:int write(int fd,void *buf,size_t nbytes);
函数参数:
– fd :要写入的文件的文件描述符
– buf: 指向内存块的指针,从这个内存块中读取数据写
入 到文件中
– nbytes: 要写入文件的字节个数
返回值
如果出现错误,返回-1
如果写入成功,则返回写入到文件中的字节个数
write()示例
#include <>
#include <>
#include <sys/>
void main()
int outfd = 0, r_size = 0;
char buf[ ] = “Hello world!!”;
outfd = open(“first”,O_WONLY | O_TRUNC | O_CREAT,S_IRWXU);
if(outfd>0)
{
r_size = write(outfd,buf,sizeof(buf));
if(r_size>0)
{
printf(“write data to file success”);
}
close(outfd);
}
}
close系统调用
• 为了重新利用文件描述符,用close()系统
调用释放打开的文件描述符
函数原型:int close(int fd);
函数参数:
– fd :要关闭的文件的文件描述符
返回值
如果出现错误,返回-1
调用成功返回0
文件的随机读写
• 到目前为止的所有文件访问都是顺序访问。这是
因为所有的读和写都从当前文件的偏移位置开始,
然后文件偏移值自动地增加到刚好超出读或写结
束时的位置,使它为下一次访问作好准备。
• 有个文件偏移这样的机制,在Linux系统中,随机
访问就变得很简单,你所需做的只是将当前文件
移值改变到有关的位置,它将迫使一次read()或write
()发生在这一位置。(除非文件被O_APPEND打开,
在这种情况下,任何write调用仍将发生在文件结
束处)
lseek系统调用 2-1
• 功能说明:通过指定相对于开始位置、当前位置或
末尾位置的字节数来重定位 curp,这取决于
lseek() 函数中指定的位置
• 原型:
off_t lseek (int fd, off_t offset, int base);
需设置的文件标识符 偏移量 搜索的起始位置
返回值:返回新的文件偏移值
lseek系统调用 2-2
• base 表示搜索的起始位置,有以下几个值:
(这些值定义在<>)
base 文件位置
SEEK_SET 从文件开始处计算偏移
SEEK_CUR 从当前文件的偏移值计算偏移
SEEK_END 从文件的结束处计算偏移
chmod和fchmod系统调用
• 功能说明:用来改变给定路径名pathname的
文件的权限位
• 原型:
int chmod (char *pathname, mode_t mode);
文件的路径名 权限位
返回值:调用成功返回0,失败返回-1
int fchmod (int fd, mode_t
mode);
文件描述符 权限位
chown和fchown系统调用
• 功能说明:用来改变文件所有者的识别号
(owner id)或者它的用户组识别号(group ID)
• 原型:
int chown (char *pathname, uid_t owner,gid_t
group);
文件的路径名 所有者识别号
返回值:调用成功返回0,失败返回-1
int fchown (int fd, uid_t owner,gid_t group);
文件描述符
用户组识别号
所有者识别号 用户组识别号
mkdir系统调用
• 功能说明:用来创建一个称为pathname的新
目录,它的权限位设置为mode
• 原型:
int mkdir(char *pathname,mode_t mode);
返回值:调用成功返回0,失败返回-1
文件的路径名 权限位
rmdir系统调用
• 功能说明:删除一个空目录
• 原型:
int rmdir(char *pathname);
返回值:调用成功返回0,失败返回-1
文件的路径名
目录访问 3-1
• 功能说明:打开一个目录
• 原型:
DIR* opendir(char *pathname);
返回值:
打开成功,返回一个目录指针
打开失败,则返回0
文件的路径名
目录访问 3-2
• 功能说明:访问指定目录中下一个连接的细
节
• 原型:
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-3
• 功能说明:关闭一个已经打开的目录
• 原型:
int closedir (DIR *dirptr);
返回值:调用成功返回0,失败返回-1
目录指针
目录操作示例
#include <sys/>
#include <>
#include <>
#include <>
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(){
if(my_read_dir(argv[1]) < 0 ){
exit(1);
}
return 0;
}
文件记录锁
• 什么是文件锁:当有多个进程同时对某一
文件进行操作时,就有可能发生数据的不
同步,从而引起错误,该文件的最后状态
取决于写该文件的最后一个程序。
• Linux中文件记录锁可以对文件某一区域进
行文件记录锁的控制。它是通过fcntl函数来
实现的。
fcntl函数
• 功能说明:管理文件记录锁的操作
• 原型:
#include <>
#include <>
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时表示从起点开始直至最大可能位置为止。
fcntl()示例2-1
#include <sys/>
#include <sys/>
#include <>
#include <>
#include <>
int main(){
int fd;
struct flock lock;
if((fd = open("example",O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) ==
-1){
printf("open file error\n");
return -1;
}
memset(&lock,0,sizeof(struct flock));
_start = SEEK_SET;
_whence = 0;
_len = 0;
fcntl()示例2-2
if(fcntl(fd,F_GETLK,&lock) == 0){
if(_type != F_UNLCK){
printf("lock can not by set in fd\n");
}else{
_type = F_WRLCK;
if(fcntl(fd,F_SETLK,&lock) == 0)
printf("set write lock success!\n");
else
printf("set write lock fail!\n");
getchar();
_type = F_UNLCK;
fcntl(fd,F_SETLK,&lock);
}
}
close(fd);
return 0;
}
课后练习
• 采用文件系统调用方式,完成串口通讯配
置文件的读写
–串口配置文件格式:
#串口设备名
dev=/dev/ttyS0
#波特率
speed=115200
#硬件流控
hw_flowctrl=0
sw_flowctrl=0
#数据位
data=8
#奇偶校验位
parity=0
#停止位
stop=1
课后练习(续)
–要求如下:
• 1、程序运行命令格式:
serialchat [-options];
[options]: p-输出打印各个配置项信息,格式:参数-----参数
值;
s-进行配置项设置,输出各选项菜单(有保存和退出)让用户设
置;
f-指定配置文件名,并输出各配置项信息
其他选项提示该程序用法帮助。
• 2、不支持serialchat运行两次 锁配置文件
(读取看是否已锁)
• 3、如果无法找到配置文件,则提示运行失败,
原因:配置文件无法找到