Linux系统编程-文件操作(黑马笔记)
1.区分系统调用和库函数
库函数是我们一开始进行学习就接触到的函数,是c语言中已经包装好的函数,所以这里不多叙述。但是我们要理解系统调用,系统调用是用户进入“内核态”的门(进入内核态 = 让操作系统“亲自出马”,帮你完成只有它才能做的底层、敏感、特权操作)。只有使用系统调用,我们写的c语言程序才能真正的进入内核态,用系统提供的函数来完成我们想做的工作。
总结一句话:我们可以通过系统调用进入内核态,并且真正调用内核中提供的函数,这就是系统调用。
系统调用 是 用户程序进入内核 的“门”;
库函数 是 在用户空间 帮你“包装”这些门、或额外提供功能的“工具箱”。
📌 具体区别(对照表)
| 维度 | 系统调用(System Call) | 库函数(Library Function) |
|---|---|---|
| 所在空间 | 内核空间 | 用户空间 |
| 调用方式 | 触发软中断(syscall 指令) | 普通函数调用(call 指令) |
| 执行权限 | 需要切换到内核态(ring 0) | 用户态即可(ring 3) |
| 执行速度 | 慢(用户↔内核切换开销) | 快(无上下文切换) |
| 功能范围 | 直接与硬件、资源交互(如文件、进程、网络) | 可以是系统调用的封装,也可以纯计算(如字符串处理) |
| 例子 | open, read, write, fork, execve | fopen, fread, printf, malloc, strlen |
| 来源 | Linux 内核提供 | libc(如 glibc)或其他库提供 |
| 可移植性 | 依赖操作系统(Linux 特有) | 标准库函数跨平台(如 ANSI C) |
✅ 总结一句话:
系统调用是“内核提供的最小功能单元”,库函数是“程序员友好的封装或扩展”。
写系统程序时,优先用库函数(更简单、更安全),需要精细控制或性能时再直接用系统调用。
2.open和close函数
所有函数都可以通过查看手册的方式进行学习,不过难度比较大,这里贴出打开手册的方法,供有志者学习。
man 2 openopen 函数
int open(char *pathname, int flags);
pathname:要打开的文件路径
flags :文件打开方式
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_CREAT 不存在则创建
O_APPEND 追加写
O_TRUNC 清空再写
O_EXCL 必须创建且文件不存在
O_NONBLOCK 非阻塞
返回值
成功:文件描述符(非负整数)
失败:-1,并设置 errno
三参数原型
int open(char *pathname, int flags, mode_t mode);
参数
pathname:文件路径
flags :同上
mode :仅当 flags 含 O_CREAT 时生效(八进制)
例:0664 → rw-rw-r--
最终权限 = mode & ~umask
“最终权限 = mode & ~umask” 就是 “你给的权限先去掉 umask 禁止的位,才是真正落盘的权限。” 这里是二进制,我们用二进制的每一位的0,1来代表是否打开权限。
返回值
成功:文件描述符
失败:-1,并设置 errno
close函数
close 函数
int close(int fd);
demo
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>int main(int argc, char *argv[])
{int fd;/* 1. 权限:读+写;2. 不存在则创建;3. 若已存在则清空 */fd = open("./dict.cp",O_RDWR | O_CREAT | O_TRUNC,0644); /* rw-r--r-- *//* 4. 必须检查返回值 */if (fd == -1) {//stderr标准输入流 默认是终端fprintf(stderr, "open failed: %s\n", strerror(errno));return 1;}printf("fd = %d\n", fd);/* 5. 关闭同样检查错误(虽然极少失败,但养成习惯) */if (close(fd) == -1) {//错误处理函数perror("close");return 1;}return 0;
}拓展部分
2.1文件描述符:
相当于拿到了进程中文件的唯一id,用这个id去控制文件

2.2预读入和缓输出

预读入:提前把用户还没要的数据块从磁盘读进页缓存,等你真正
read()时直接命中内存,磁盘几乎零等待。缓输出:先把用户
write()的数据写进页缓存就立即返回,内核稍后再异步刷盘(或按fsync/O_SYNC强制落盘)。
2.3阻塞和非阻塞(设备文件或者网络文件)
算是设备文件或者网络文件的一种属性,规定了这两种文件如果被读失败时的处理方式
阻塞:阻塞掉CPU等待输入
非阻塞:返回错误信息,不会阻塞。
设置文件的阻塞和非阻塞
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>int set_nonblock(int fd, int on) {int flags = fcntl(fd, F_GETFL);if (flags == -1) return -1;if (on)flags |= O_NONBLOCK;elseflags &= ~O_NONBLOCK;//设置flag,成功返回1,否则返回0return fcntl(fd, F_SETFL, flags);
}int main() {int fd = open("fifo", O_RDWR);if (fd == -1) { perror("open"); return 1; }set_nonblock(fd, 1); /* 设为非阻塞 *//* ... 读写 ... */set_nonblock(fd, 0); /* 恢复阻塞 */close(fd);return 0;
}3.read和write函数
read 函数
原型:
ssize_t read(int fd, void *buf, size_t count);
参数:
fd 文件描述符
buf 存放读入数据的缓冲区
count 缓冲区大小(最多读多少字节)
返回值:
成功:实际读到的字节数(可能 < count)
失败:-1,并设置 errno
-1:并且errno=BAGIN或EWOULDBLOCK,说明不是read失败,而是被阻塞(设备文件或者网络文件)
write 函数
原型:
ssize_t write(int fd, const void *buf, size_t count);
参数:
fd 文件描述符
buf 待写入数据的缓冲区
count 要写入的字节数
返回值:
成功:实际写入的字节数(可能 < count)
失败:-1,并设置 errno
demo(实现copy效果)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>int main(int argc, char *argv[]) int fd1 = open(argv[1], O_RDONLY);if (fd1 == -1) { perror("open src"); return 1; }//以读写打开,有就截断为空,没有就是create,0664是创建的权限int fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664);if (fd2 == -1) { perror("open dst"); close(fd1); return 1; }char buf[1024]; /* 64K 缓冲更快 */ssize_t n;while ((n = read(fd1, buf, sizeof(buf))) > 0) {if (write(fd2, buf, n) != n) { perror("write"); break; }}if (n == -1) perror("read");close(fd1);close(fd2);return 0;
}4.lseek函数
lseek 函数(控制光标的偏移)
off_t lseek(int fd, off_t offset, int whence);
参数:
fd 文件描述符
offset 偏移量(字节数)
whence 偏移基准
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾
返回值:
成功:返回新的偏移量(相对于文件起始位置的字节数)
失败:-1,并设置 errno
demo(1. 文件的“读”和“写”使用同一偏移位置。)
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>int main(void)
{int fd, n;char msg[] = "It's a test for lseek\n";char ch;fd = open("lseek.txt", O_RDWR | O_CREAT, 0644);if (fd < 0) {perror("open lseek.txt error");exit(1);}write(fd, msg, strlen(msg));/* 使用 fd 对打开的文件进行写操作,文件读写位置位于文件结尾处 */lseek(fd, 0, SEEK_SET);/* 修改文件读写指针位置,位于文件开头。注释该行会怎样呢? */while ((n = read(fd, &ch, 1))) {if (n < 0) {perror("read error");exit(1);}/* 此处缺少输出或处理逻辑,可自行补充 */}close(fd);return 0;
}demo(2. 使用 lseek 获取文件大小。)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>int main(int argc, char *argv[])
{int fd = open(argv[1], O_RDWR);if (fd == -1) {perror("open error");exit(1);}int length = lseek(fd, 0, SEEK_END);printf("file size: %d\n", length);close(fd);return 0;
}demo(3. 使用 lseek 拓展文件大小:要想使文件大小真正拓展,必须引起一次 IO 操作(如 write 一个字节
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main(int argc, char *argv[])
{int fd = open(argv[1], O_RDWR);if (fd == -1) {perror("open error");exit(1);}int length = lseek(fd, 110, SEEK_END);printf("file size:%d\n", length);//引起真正的io操作,否则只是简单的偏移。write(fd, "a", 1);close(fd);return 0;
}5.inode和detry
总结一句话是:inode本身是文件的一种数据结构,他记录了许多文件的属性。dentry是目录项一个目录中有许多dentry,dentry记录了文件名->inode的映射。
inode(索引节点)
inode 是文件系统中的一个数据结构,用于存储文件的元数据(metadata),但不包括文件名。
元数据 包括文件的权限、所有者、大小、创建时间、修改时间、访问时间、文件类型(如普通文件、目录、符号链接等)、以及指向文件数据块的指针等。
每个文件在文件系统中都有一个唯一的 inode 号,通过这个 inode 号可以找到文件的 inode,进而访问文件的元数据和数据块。
dentry(目录项)
dentry 是目录中的一个条目,它将文件名与 inode 号关联起来。
目录际上是一个特殊的文件,其中包含了多个 dentry,每个 dentry 包含一个文件名和一个指向 inode 的指针。
· 当你访问一个文件时,系统首先在目录中查找对应的 dentry,通过 dentry 中的 inode 号找到文件的 inode,然后通过 inode 访问文件数据。
6.lstat和stat函数
其实就是用文件名获得inode,通过inode结构体获取文件的一些属性。
stat/lstat 函数:
int stat(const char *path, struct stat *buf);
参数:
path:文件路径
buf:(传出参数)存放文件属性。
返回值:
成功:0
失败:-1 errno
获取文件大小:buf.st_size
获取文件类型:buf.st_mode
获取文件权限:buf.st_mode
符号穿透:stat会。lstat不会。
demo
#include <sys/stat.h>
#include <pthread.h>int main(int argc, char *argv[])
{struct stat sb;int ret = stat(argv[1], &sb);if (ret == -1) {perror("stat error");exit(1);}if (S_ISREG(sb.st_mode)) {printf("It's a regular\n");} else if (S_ISDIR(sb.st_mode)) {printf("It's a dir\n");} else if (S_ISFIFO(sb.st_mode)) {printf("It's a pipe\n");} else if (S_ISLNK(sb.st_mode)) {printf("it's a sym link\n");}return 0;
}文件权限位

7.link和unlink函数
link函数

硬链接和软连接的区别主要是在,硬链接是不同的dentry但还是相同的inode,相当于给文件取了一个别名(我的理解)而软连接是新创建了一个文件用于链接可以类比windows中的快捷方式。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>int main(int argc, char *argv[])
{link(argv[1], argv[2]);unlink(argv[1]);return 0;
}unlink函数(调用unlink后并没有直接删除,而是系统择机删除)

#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>int main(void)
{int fd;char *p = "test of unlink\n";char *p2 = "after write something.\n";fd = open("temp.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd < 0){perror("open temp error");exit(1);}int ret = write(fd, p, strlen(p));if (ret == -1) {perror("----write error");}printf("hi! I'm printf\n");ret = write(fd, p2, strlen(p2));if (ret == -1) {perror("----write error");}printf("Enter anykey continue\n");getchar();close(fd);int unlink_ret = unlink("temp.txt");if(unlink_ret < 0){perror("unlink error");exit(1);}return 0;
}