- A+
库函数和系统调用
库函数调用 | 系统调用 |
---|---|
在所有的ANSI C编译器中,C库函数都是相同的 | 各个操作系统的系统调用是不同的,这导致程序不可移植 |
它调用库函数中的一段程序(或函数) | 它调用系统内核的服务 |
与用户程序相联系 | 在内核地址空间执行 |
它的运行时间属于“用户时间” | 运行时间属于“系统时间” |
属于过程调用,调用开销较小 | 需要在用户控件和内核 上下文环境切换,开销较大 |
在C函数库libc中大约有300个函数 | 在UNIX中大约有90个系统调用 |
典型的C函数库:printf、fopen、fread、malloc | 典型的系统调用:write、open、read、sbrk、fork |
文件I/O系统调用
文件和文件描述符
文件扩展名
在Linux中,扩展名对Linux内核没有实际意义,但是可以用来人为区分不同的文件,方便用户使用。
- .tar,.tar.gz,.tgz,zip,.tar.bz表示压缩文件,创建命令为tar,gzip,unzip等
- .sh文件表示shell脚本文件
- .pl 表示perl语言文件
- .py 表示python语言文件
- .conf 表示系统服务的配置文件
- .c表示C文件
- .h头文件
- .cpp表示C++源文件
- .so 表示动态库文件
- ·a 表示静态库文件
文件类型
Linux系统中把一切都看做文件,Linux有7中类型文件:普通文件-、目录(dierectory)文件、符号(link)链接、字符(character)设备文件、块(block)设备文件、管道(pipe)文件、套接字(socket)文件。其中文件、目录、符号链接会占用磁盘空间来存储,而块设备、字符设备、套接字、管道是伪文件,并不占用磁盘空间。
通过ls -l 命令查看文件类型
文件类型标识 | 文件类型 |
---|---|
- | 普通文件 |
d | 目录文件 |
l | 符号链接 |
c | 字符设备 |
b | 块设备 |
p | 管道 |
s | 套接字socket |
文件描述符
文件描述符(file descriptor, fd) 是Linux内核为了高效管理已被打开的文件所创建的索引, 其是一个非负整数(通常是小整数) , 用于指代被打开的文件, 所有执行I/O操作的系统调用都通过文件描述符。程序在开始运行时,系统会自动打开三个文件描述符, 0是标准输入, 1是标准输出, 2是标准错误。POSIX标准要求每次打开文件时(含socket) 必须使用当前进程中最小可用的文件描述符号码, 因此第一次打开的文件描述符一定是3.
文件描述符 | 用途 | POSIX文件描述符 | 标准I/O文件流 |
---|---|---|---|
0 | 标准输入 | STDIN_FILENO | stdin |
1 | 标准输出 | STDOUT_FILENO | stdout |
2 | 标准出错 | STDERR_FILENO | stderr |
文件I/O操作
程序编写
/*头文件包含,可以通过man手册查找*/ #include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> /*MSG_STR是写到文件中的内容,是一个常量,会保存到程序的只读数据段中*/ #define BUFSIZE 1024 #define MSG_STR "Hello Worldn" int main(int argc,char *argv[]) { int fd = -1; //文件描述符,正常应该为非负的整数 int rv = -1; //返回值return value char buf[BUFSIZE]; //存放从文件中读到的数据 fd = open("test.txt",O_RDWR|O_CREAT|O_TRUNC,0666); //调用open()系统调用并返回一个文件描述符保存在fd中 if(fd < 0) { /*perror()函数可以打印出错的原因,但是不能处理要进行格式化控制的输出或写入日志文件 */ perror("Open/Creat file test.txt failure"); return 0; } printf("Open file returned file descriptor [%d]n",fd); /* write(写到哪里,写什么,写的大小) *用strlen是因为MSG_STR是宏定义,宏定义是地址*/ if((rv = write(fd,MSG_STR,strlen(MSG_STR))) < 0) { /*strerror()函数可以将整型类型的出错原因errno转换成相应的字符串形式*/ printf("Write %d bytes into file failure: %sn",rv,strerror(errno)); goto cleanup; //在做错误处理时一般会用goto,做集中的错误处理 } /*test.txt里有成功写入,但总是Read 0 bytes data from file,这是因为在Hello World写入后,文件偏移量(类似光标)位于World的后方,读或写入时从光标所在位置开始读写,所以读不到值,通过lseek()系统调用将文件偏移量设置到第一个字节后,Read 12 bytes data from file: Hello World */ //lseek(fd, 0, SEEK_SET); /*buf在栈中,栈中的数据是随机数,在使用buf时不对buf进行初始化,会导致后面打印出的是个随机数。因此在往buf中写内容前要先用memset将buf中的内容清掉*/ memset(buf,0,sizeof(buf)); if( (rv=read(fd,buf,sizeof(buf))) < 0 ) { printf("Read data from file failure: %sn",strerror(errno)); goto cleanup; } printf("Read %d bytes data from file: %sn",rv,buf); cleanup: close(fd); /*非main函数return只会导致函数退出 *main函数的return会调用exit(),导致整个进程中止 *同理,其他非main函数调用exit()一样会导致整个进程中止 *程序结束后,可以通过"echo $?"命令查看返回值*/ return 0; }
strlen()和sizeof()的区别:
sizeof()
是一个运算符,而strlen()
是一个函数。sizeof()
计算的是变量或类型所占用的内存字节数,而strlen()
计算的是字符串中字符的个数。sizeof()
可以用于任何类型的数据,而strlen()
只能用于以空字符 ' ' 结尾的字符串。sizeof()
计算字符串的长度,包含末尾的 ' ',strlen()
计算字符串的长度,不包含字符串末尾的 ' '。
程序编译运行
li@ubuntu22:~/apue$ gcc file_io.c -o file_io li@ubuntu22:~/apue$ ./file_io Open file returned file descriptor [3] Read 0 bytes data from file: li@ubuntu22:~/apue$ ./file_io Open file returned file descriptor [3] #再运行一次还是3,描述符是当前进程最小可用的,第二次运行是新的进程 Read 0 bytes data from file: li@ubuntu22:~/apue$ cat test.txt #通过cat test. txt命令查看文件内容验证 "Hello World"字符串确实被wirte()系统调用写入到了文件中 Hello World
文件I/O操作函数
open()系统调用
int open(const char *path, int :flag, ... /*mode_t mode*/);
open()系统调用用来打开一个文件, 并返回一个文件描述符(file description), 并且该文件描述符是当前进程,最小、未使用的文件描述符数值。
参数: path: 要打开的文件、设备的路径
flag: 由多个选项进行 “或”运算构造flag参数。
必选:O_RDONLY(只读)、 O_WRONLY(只写)、 O_RDWR(读写)
可选:O_APPEND 每次写时都追加到文件的尾端。
O_CREAT 文件不存在则创建它, 使用该选项需要第三个参数mode
O_TRUNC 如果文件存在,而且为只写或读写成功打开,则将其长度截取为0
O_NONBLOCK 如果path是一个FIFO、块设备、字符特殊文件则此选项为文件的本次打开和后续的I/O操作设置非阻塞模式方式。
O_EXEC、O_SEARCH、O_CLOEXEC、O_NOCTTY....
mode: oflag带O_CREAT选项时可以用来创建文件, 这时必须带该参数用来指定创建文件的权限模式,如0666。否则不需要。使用示例代码:
int fd; fd = open("text. txt", O_RDWR|O_CREAT|O_TRUNC, 0666); //O_RDWR文件打开可以通过文件描述符可读可写,O_CREAT文件不存在则创建,O_TRUNC如果文件中有内容先把文件清掉 fd =open("text. txt", O_WRONLY|O_APPEND); //O_WRONLY只写,O_APPEND追加到文件尾,如果文件不存在则返回-1
creat()系统调用
/*用来创建一个新文件并返回其fd,等价于open(path, O_WRONLY|O_CREAT|O_TRUNC,mode);*/ int creat(const char *path,mode_t mode);
close()系统调用
/*用来关闭一个打开的文件描述符*/ int close(int fd);
write()系统调用
/*用来往打开的文件描述符fd指向的文件中写入buf指向的数据,nbytes指定要写入的数据大小*/ size_t write(int fd,const void *buf,size_t nbytes);
read()系统调用
/*用来从打开的文件描述符对应的文件中读取数据放到buf指向的内存空间中去,最多不要超过nbytes个字节,nbytes一般是buf剩余的空间大小*/ size_t read(int fd,void *buf,size_t nbytes);
lseek()系统调用
off_t lseek(int fd, off_t offset, int whence);
我们在从文件里读出内容, 或往文件写如内容的时候都有一个起始地址, 这个起始地址就是当前文件偏移量,当我们对文件进行读写的时候都会使文件偏移量往后偏移。这点就类似于我们打开记事本开始编辑文本时的光标, 我们读或写入时从光标所在位置开始读写, 每读写一个字节都会使光标往后偏移。通过lseek()这个函数我们可以调整文件偏移量的地址。
其中 whence 可以是以下三个值
whence | 位置 |
---|---|
SEEK_SET | 文件头 |
SEEK_CUR | 当前位置 |
SEEK_END | 文件尾 |
而offset就是相对于whence 的偏移量, 譬如:
lseek(fd, 0, SEEK_SET); 将文件偏移量设置到了文件开始的第一个字节上
lseek(fd, 0, SEEK_END); 将文件偏移量设置到文件最后一个字节上
lseek(fd, -1, SEEK_END); 将文件偏移量设置到文件最后的倒数第一个字节上
dup()和dup2()系统调用
int dup(int fd); int dup2(int fd,int fd2);
这两个函数都可以用来复制一个新的文件描述符来指向fd对应的文件。这两个系统调用经常用在标准输入、标准输出、标准出错重定向。
dup0返回的新文件描述符一定是当前可用文件描述符中的最小数值
dup2可以用fd2参数来指定新的文件描述符。如果fd2已经打开,则先关闭。如fd等于fd2,则dup2返回fd2,而不关闭它。
#include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc,char *argv[]) { int fd = -1; fd = open("std.txt",O_RDWR|O_CREAT|O_TRUNC,0666); if( fd < 0 ) { printf("Open file failure: %sn",strerror(errno)); return 0; } close(0); //用dup()先close() close(1); close(2); dup(fd); //0 标准输入 dup(fd); //1 标准输出 dup(fd); //2 标准错误输出 // dup2(fd, STDIN_FILENO); // 标准输入重定向到fd // dup2(fd, STDOUT_FILENO); // 标准输出重定向到fd // dup2(fd, STDERR_FILENO); // 标准错误输出重定向到fd printf("fd=%dn",fd); close(fd); return 0; }
li@ubuntu22:~/apue$ vim redirect_stdio.c li@ubuntu22:~/apue$ gcc redirect_stdio.c -o redirect_stdio li@ubuntu22:~/apue$ ./redirect_stdio li@ubuntu22:~/apue$ echo $? 0 li@ubuntu22:~/apue$ ls file_io file_io.c redirect_stdio redirect_stdio.c std.txt test.txt li@ubuntu22:~/apue$ cat std.txt fd=3
stat()和fstat()系统调用
int stat(const char *restrict path,struct stat *restrict buf); int fstat(int fd,struct stat *buf);
用于返回文件或目录相关信息,stat()第一个参数是文件名,而fstat()第一个参数是文件打开的相应文件描述符。
struct stat结构体的定义:
struct stat { dev_t st_dev; // 文件的设备 ID ino_t st_ino; // 文件的 inode 号 mode_t st_mode; // 文件类型和权限 nlink_t st_nlink; // 文件的硬链接数目 uid_t st_uid; // 文件所有者的用户 ID gid_t st_gid; // 文件所有者的组 ID dev_t st_rdev; // 设备文件的设备 ID off_t st_size; // 文件总大小(以字节为单位) blksize_t st_blksize; // 文件系统的 I/O 缓冲区大小 blkcnt_t st_blocks; // 分配给文件的磁盘块数 time_t st_atime; // 最后一次访问时间 time_t st_mtime; // 最后一次修改时间 time_t st_ctime; // 最后一次更改时间(修改文件的权限或者所有者) };
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> int main(int argc,char **argv) { struct stat stbuf; stat("stat.c",&stbuf); /*stbuf是一个指针,在栈区,它指向的地址是一个随机地址(野指针),使用指针一定要先初始化: 1、 使用malloc struct stat *stbuf; stbuf = malloc(sizeof(struct stat)) 2、定义一个变量stbuf,传&stbuf struct stat stbuf; stat("stat.c",&stbuf); */ //struct stat *stbuf; //stat("stat.c",stbuf); /*fstat()要先用open()后使用*/ // int fd = -1; // fd = open("stat.c",O_RSONLY); // fstat(fd,stbuf); printf("File Mode: %o Real Size: %luB,Space Size: %luBn",stbuf.st_mode,stbuf.st_size,stbuf.st_blksize); return 0; }
li@ubuntu22:~/apue$ vim stat.c li@ubuntu22:~/apue$ gcc stat.c -o stat li@ubuntu22:~/apue$ ./stat File Mode: 100664 Real Size: 283B,Space Size: 4096B li@ubuntu22:~/apue$
access()系统调用
int access(const char *path,int mode)
用来测试文件是否存在或测试其权限位,其中第一个参数path是相应的文件路径名,第二个参数是要测试的模式。
mode说明:
模式 | 说明 |
---|---|
R_OK | 测试读许可权 |
W_OK | 测试写许可权 |
X_OK | 测试执行许可权 |
F_OK | 测试文件是否存在 |
#include <stdio.h> #include <unistd.h> #define TEST_FILE "access.c" int main(int argc,char *argv[]) { //测试文件是否存在 if(access(TEST_FILE,F_OK) != 0) { printf("File %s not exist!n",TEST_FILE); return 0; } printf("File %s not exist!n",TEST_FILE); //测试读许可权 if(access(TEST_FILE,R_OK)==0) { printf("READ OKn"); } //测试写许可权 if(access(TEST_FILE,W_OK)==0) { printf("WRITE OKn"); } //测试执行许可权 if(access(TEST_FILE,X_OK)==0) { printf("EXEC OKn"); } return 0; }
li@ubuntu22:~/apue$ ./access File access.c not exist! READ OK WRITE OK
unlink()系统调用
用来删除文件,其本质是让文件的链接记数自减,当链接记数减为0时,文件会被自动删除。(Linux下rm命令其实就是调用了unlink()系统调用)
rename()系统调用
将文件重命名
文件夹操作相关系统调用
函数原型 | 函数说明 |
---|---|
int mkdir(const char *pathname,mode_t mode) |
创建文件夹 |
int rmdir(const char *pathname) |
删除文件夹(文件夹必须为空) |
DIR *opendir(const char *pathname) |
打开文件夹 |
struct dirent *readdir(DIR *dp) |
读文件夹 |
int closedir(DIR *dp) |
关闭文件夹 |
int chdir(const char *pathname) |
改变工作目录 |
readdir()系统调用的struct dirent:
struct dirent { long d_ino; /* inode number 索引节点号 */ off_t d_off; /* offset to this dirent 在目录文件中的偏移 */ unsigned short d_reclen; /* length of this d_name 文件名长 */ unsigned char d_type; /* the type of d_name 文件类型 */ char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */ }
#include <stdio.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define TEST_DIR "dir" int main(int argc,char *argv[]) { int rv = 0; int fd1 = -1; int fd2 = -1; DIR *dirp = NULL; struct dirent *direntp = NULL; /* 创建文件夹,文件权限755 */ if( mkdir(TEST_DIR,0755) < 0 ) { printf("creat directory '%s' failure: %sn",TEST_DIR,strerror(errno)); return -1; } /* 更改当前工作路径到文件夹dir下 */ if( chdir(TEST_DIR) < 0 ) { printf("change directory '%s' failure: %sn",TEST_DIR,strerror(errno)); rv = -2; goto cleanup; } /* 在dir文件夹下 创建普通文本文件file1.txt,并设置其权限位为644 */ if( (fd1 = creat("file1.txt",0644)) < 0 ) { printf("creat file1.txt failure: %sn",strerror(errno)); rv = -3; goto cleanup; } /* 在dir文件夹下 创建普通文本文件file2.txt,并设置其权限位为644 */ if( (fd1 = creat("file2.txt",0644)) < 0 ) { printf("creat file2.txt failure: %sn",strerror(errno)); rv = -4; goto cleanup; } /* 更改当前工作路径到父目录去 */ if( chdir("../") < 0 ) { printf("change directory to '%s' failure: %sn",TEST_DIR,strerror(errno)); rv = -5; goto cleanup; } /* 打开dir文件夹 */ if ((dirp = opendir(TEST_DIR)) == NULL) { printf("opendir %s error: %sn", TEST_DIR, strerror(errno)); rv = -6; goto cleanup; } /* 列出dir里面的所有文件和文件夹,一个文件夹下至少要有.和..两个文件,从文件夹下读时,每个文件都是一个dirent*/ while((direntp = readdir(dirp)) != NULL) { printf("find file: %sn",direntp->d_name); } /* 关闭所有打开的文件夹 */ closedir(dirp); return rv; cleanup: if(fd1 >= 0) { close(fd1); } if(fd2 >= 0) { close(fd2); } }
li@ubuntu22:~/apue$ vim dir.c li@ubuntu22:~/apue$ gcc dir.c -o dir li@ubuntu22:~/apue$ ./dir find file: file1.txt find file: .. find file: file2.txt find file: . li@ubuntu22:~/apue$ ls directory -la total 8 drwxr-xr-x 2 li li 4096 9月 24 21:16 . drwxrwxr-x 3 li li 4096 9月 24 21:16 .. -rw-r--r-- 1 li li 0 9月 24 21:16 file1.txt -rw-r--r-- 1 li li 0 9月 24 21:16 file2.txt