设备与文件
Linux采用文件系统管理硬件设备,所有的设备都看成是特殊的文件,从而将硬件设备的特性及管理细节对用户隐藏起来,实现设备无关性。
设备管理的特点
①每个设备都对应文件系统中的一个索引节点,都有一个文件名。
②应用程序通常可以通过系统调用open( )打开设备文件,建立起与目标设备的连接。
③对设备的使用类似于对文件的存取。
④设备驱动程序都是系统内核的一部分,它们必须为系统内核或者它们的子系统提供一个标准的接口。
⑤设备驱动程序使用一些标准的内核服务,如内存分配等。
设备工作原理
设备分类
按设备属主关系:系统设备、用户设备。
按设备信息交换单位来分:字符设备、块设备。
按设备共享属性来分:独享设备、共享设备。
Linux设备操作
设备或文件操作两种方式:用户编程接口 API、系统调用。
系统调用
系统调用是操作系统提供给用户的一组“特殊”接口。
系统调用并非直接和程序员或系统管理员直接打交道,而是通过软中断的方式向内核提交请求,从而获取内核函数的服务入口(系统调用表)。
系统调用让系统从用户空间进入内核空间内运行,运行后将结果返回给应用程序(内核态–>用户空间)。
系统调用和系统API等区别
系统API
主要是通过C库libc来实现,程序员多采用这种方式与内核交互,这些API通过系统调用来实现。
系统命令
系统管理员采用系统命令与内核交互,是一个可执行文件,通过系统API及系统调用来实现。
外壳程序
一系列系统命令和SHELL脚本共同组合成的程序。
函数库调用与系统调用
函数库调用 | 系统调用 |
---|---|
在所有啊ANSIC编译器版本中,C库函数是相同的 | 各个操作系统的系统调用是不同的 |
它调用函数库中的一段程序(或函数) | 它调用系统内核的服务 |
与用户程序相联系 | 是操作系统的一个入口函数 |
在用户地址空间执行 | 在内核地址空间执行 |
它的运行时间属于“用户时间” | 它的运行时间属于“系统”时间 |
属于过程调用,调用开销较小 | 需要在用户空间和内核上下文环境间切换,开销较大 |
在C函数库libc中大约300个函数 | 在UNIX中大约有90个系统调用 |
典型的C函数库调用:system fprintf malloc | 典型的系统调用:chdir fork write brk |
C库的文件操作
函数名 | 功能 |
---|---|
fopen( ) | 打开文件 |
fclose( ) | 关闭文件 |
fputc( ) | 将字符写入文件中 |
fgetc( ) | 从文件中读取字符 |
fread( ) | 将数据从文件中读到缓冲区 |
fwrite( ) | 将数据从缓冲区写入文件 |
fseek( ) | 在文件中搜索指定位置 |
fprintf( ) | 操作类似于 printf(),但是用于文件 |
fscanf( ) | 操作类似于 scanf(),但是用于文件 |
feof( ) | 如果到达文件结尾,返回 true |
ferror( ) | 如果出错,返回 true |
rewind( ) | 将文件位置指示器重新置于文件开头 |
remove( ) | 删除文件 |
fflush( ) | 将内部缓冲区的数据写入指定文件 |
文件描述符fd
每个进程PCB结构中有文件描述符指针,指向files_struct的文件描述符表,记录每个进程打开的文件列表。
系统内核不允许应用程序访问进程的文件描述符表,只返回这些结构的索引即文件描述符ID(File Description)给应用程序。
Linux系统中,应用程序通过这些文件描述符来实现让内核对文件的访问每个进程能够访问的文件描述符是有限制的,通过ulimit –n
可以查看。
在Linux系统中有三个已经被分配的文件描述符,分别是:
0 STDIN_FILENO 标准输入流
1 STDOUT_FILENO 标准输出流
2 STDERR_FILENO 标准错误流
这三个文件描述符和它们各自的功能是绑死的,每个进程被加载后,默认打开0,1,2这三个文件描述符。
系统函数
open系统调用
在Linux下,用open函数可以用来打开或创建一个文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int flags);
int open(const char *path, int flags,mode_t mode);
参数:
path:文件的名称,可以包含(绝对和相对)路径
flags:文件打开模式
mode:用来规定对该文件的所有者,文件的用户组及系统中其他用户的访问权限,则文件权限为:mode&(~umask)
返回值:
打开成功,返回文件描述符;
打开失败,返回-1
打开文件的方式(flags的值)
O_RDONLY 只读打开
O_WRONLY 只写打开
O_RDWR 可读可写打开
O_APPEND 表示追加。如果文件已有内容,这次打开文件所写的数据附加到文件的末尾而不覆盖原来的内容。
O_CREAT 若此文件不存在则创建它。使用此选项时需要提供第三个参数mode,表示该文件的访问权限。
O_EXCL 如果同时指定了O_CREAT,并且文件已存在,则出错返回。
O_TRUNC 如果文件已存在,并且以只写或可读可写方式打开,则将其长度截断(Truncate)为0字节。
O_NONBLOCK 对于设备文件,以O_NONBLOCK方式打开可以做非阻塞I/O(Nonblock I/O),非阻塞I/O在下一节详细讲解。
访问权限(mode的值)
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
open函数与C标准I/O库的fopen函数的区别:
①以可写的方式fopen
一个文件时,如果文件不存在会自动创建,而open
一个文件时必须明确指定O_CREAT
才会创建文件,否则文件不存在就出错返回。
②以w
或w+
方式fopen
一个文件时,如果文件已存在就截断为0字节,而open
一个文件时必须明确指定O_TRUNC
才会截断文件,否则直接在原来的数据上改写。
③第三个参数mode
指定文件权限,可以用八进制数表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示。
示例:
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
void main()
{
int outfd = 0;
fd=open("test",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(fd); //关闭文件描述符
}
close系统调用
close函数关闭一个已打开的文件:
#include <unistd.h>
int close(int fd);
参数:
fd :要关闭的文件的文件描述符
返回值:
成功返回0,出错返回-1并设置errno
read系统调用
read函数从打开的设备或文件中读取数据
#include <unistd.h>
int read(int fd, void *buf, size_t nbytes);
参数:
fd :想要读的文件的文件描述符
buf: 指向内存块的指针,从文件中读取来的字节放到这个内存块中
nbytes: 从该文件复制到buf中的字节个数
返回值:
成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0
write系统调用
write函数向打开的设备或文件中写数据
#include <unistd.h>
int write(int fd, const void *buf, size_t nbytes);
参数:
fd :要写入的文件的文件描述符
buf: 指向内存块的指针,从这个内存块中读取数据写入到文件中
nbytes: 要写入文件的字节个数
返回值:
成功返回写入的字节数,出错返回-1并设置errno
示例:
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
void main()
int outfd = 0, r_size = 0;
char buf[ ] = "Hello world!!";
fd = open("test",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(fd);
}
}
lseek系统调用
lseek和标准I/O库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int base);
//这里允许偏移超过文件末尾,中间空出来的位置都是0.
参数:
fd:需设置的文件标识符
offset:偏移量
base:搜索的起始位置
返回值:
返回新的文件偏移值
base 表示搜索的起始位置,有以下几个值:
SEEK_SET,从文件开始处计算偏移
SEEK_CUR,offset为相对当前位置的位置
SEEK_END,offset为相对文件结尾的位置
若lseek成功执行,则返回新的偏移量,因此可用以下方法确定一个打开文件的当前偏移量。如:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
fcntl系统调用
这里先引进文件记录锁的概念:当有多个进程同时对某一文件进行操作时,就有可能发生数据的不同步,从而引起错误,该文件的最后状态取决于写该文件的最后一个程序。
Linux中文件记录锁可以对文件某一区域进行文件记录锁的控制。它是通过fcntl函数来实现的,fcntl是管理文件记录锁的操作。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int fd;
struct flock lock;
if((fd = open("test",O_CREAT | O_TRUNC | O_RDWR, S_IRWXU)) == -1)
{
printf("open file error\n");
return -1;
}
memset(&lock,0,sizeof(struct flock));
lock.l_start = SEEK_SET;
lock.l_whence = 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;
}