字符设备和块设备都体现了“一切都是文件”的设计思想,掌握Linux文件系统、设备文件系统的知识很重要。
首先,驱动最终通过与文件操作相关的系统调用或C库函数(本质上也基于系统调用)被访问,而设备驱动的结构最终也是为了提供给应用程序员的API。
其次,驱动工程师在设备驱动中会与设备文件系统打交道,包括从Linux 2.4内核的devfs文件系统到Linux 2.6以后的udev。
本节通过Linux API和C库函数在用户空间进行Linux文件操作的编程方法。
5.1 Linux文件操作
5.1.1 文件操作系统调用
Linux的文件操作系统调用涉及创建、打开、读写和关闭文件。
1.创建
int creat(const char *filename, mode_t mode);
参数mode指定新建文件的存取权限,它同umask一起决定文件的最终权限(mode&umask),其中,umask代表了文件在创建时需要去掉的一些存取权限。umask可通过系统调用umask()来改变:
int umask(int newmask);
该调用将umask设置为newmask,然后返回旧的umask,它只影响读、写和执行权限。
2.打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
open()函数有两个形式,其中pathname是要打开的文件名(包含路径名称,缺省是认为在当前路径下面),flags可以是表5.1中的一个值或者是几个值的组合。
表5.1 文件打开标志
O_RDONLY、O_WRONLY、O_RDWR三个标志只能使用任意的一个。
如果使用了O_CREATE标志,则使用的函数是int open(const char*pathname,int flags,mode_t mode);
还要指定mode标志,以表示文件的访问权限。mode可以是表5.2中所列值的组合。
表5.2 文件访问权限
除了可以通过上述宏进行“或”逻辑产生标志以外,也可以自己用数字来表示,Linux用5个数字来表示文件的各种权限:第一位表示设置用户ID;第二位表示设置组ID;第三位表示用户自己的权限位;第四位表示组的权限;最后一位表示其他人的权限。每个数字可以取1(执行权限)、2(写权限)、4(读权限)、0(无)或者是这些值的和。例如,要创建一个用户可读、可写、可执行,但是组没有权限,其他人可以读、可以执行的文件,并设置用户ID位,那么应该使用的模式是1(设置用户ID)、0(不设置组ID)、7(1+2+4,读、写、执行)、0(没有权限)、5(1+4,读、执行)即10705:
open("test", O_CREAT, 10705);
上述语句等价于:
open("test", O_CREAT, S_IRWXU | S_IROTH | S_IXOTH | S_ISUID );
如果文件打开成功,open函数会返回一个文件描述符,以后对该文件的所有操作就可以通过对这个文件描述符进行操作来实现。
3.读写
在文件打开以后,才可对文件进行读写,Linux中提供文件读写的系统调用是read、write函数:
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中,参数buf为指向缓冲区的指针,length为缓冲区的大小(以字节为单位)。函数read()实现从文件描述符fd所指定的文件中读取length个字节到buf所指向的缓冲区中,返回值为实际读取的字节数。函数write实现把length个字节从buf指向的缓冲区中写到文件描述符fd所指向的文件中,返回值为实际写入的字节数。
以O_CREAT为标志的open实际上实现了文件创建的功能,因此,下面的函数等同于creat()函数:
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
4.定位
对于随机文件,可以随机指定位置进行读写,使用如下函数进行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()将文件读写指针相对whence移动offset个字节。操作成功时,返回文件指针相对于文件头的位置。参数whence可使用下述值:
SEEK_SET:相对文件开头
SEEK_CUR:相对文件读写指针的当前位置
SEEK_END:相对文件末尾
offset可取负值,例如下述调用可将文件指针相对当前位置向前移动5个字节:
lseek(fd, -5, SEEK_CUR);
由于lseek函数的返回值为文件指针相对于文件头的位置,因此下列调用的返回值就是文件的长度:
lseek(fd, 0, SEEK_END);
5.关闭
操作完成以后,要关闭文件,只要调用close就可以了,其中fd是要关闭的文件描述符:
int close(int fd);
例程:编写一个程序,在当前目录下创建用户可读写文件hello.txt,在其中写入“Hello World”,关闭该文件。再次打开该文件,读取其中的内容并输出在屏幕上。
代码清单5.1 Linux文件操作用户空间编程(使用系统调用)
#include
#include
#include
#include
#include
#define LENGTH 100
void main()
{
int fd, len;
char str[LENGTH];
fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 创建并打开文件 */
if(fd < 0){
perror("error");
return;
}
if(fd){
write(fd, "Hello World", strlen("Hello World")); /* 写入字符串 */
close(fd);
}
fd = open("hello.txt", O_RDWR);
len = read(fd, str, LENGTH); /* 读取文件内容 */
str[len] = '\0';
printf("%s\n", str);
close(fd);
}
编译并运行,执行结果为输出“Hello World”。