当前位置: 首页 > news >正文

Linux系统编程-文件操作(黑马笔记)

1.区分系统调用和库函数

库函数是我们一开始进行学习就接触到的函数,是c语言中已经包装好的函数,所以这里不多叙述。但是我们要理解系统调用,系统调用是用户进入“内核态”的门(进入内核态 = 让操作系统“亲自出马”,帮你完成只有它才能做的底层、敏感、特权操作)。只有使用系统调用,我们写的c语言程序才能真正的进入内核态,用系统提供的函数来完成我们想做的工作。

总结一句话:我们可以通过系统调用进入内核态,并且真正调用内核中提供的函数,这就是系统调用。

系统调用用户程序进入内核 的“门”;
库函数在用户空间 帮你“包装”这些门、或额外提供功能的“工具箱”。


📌 具体区别(对照表)

维度系统调用(System Call)库函数(Library Function)
所在空间内核空间用户空间
调用方式触发软中断(syscall 指令)普通函数调用(call 指令)
执行权限需要切换到内核态(ring 0)用户态即可(ring 3)
执行速度慢(用户↔内核切换开销)快(无上下文切换)
功能范围直接与硬件、资源交互(如文件、进程、网络)可以是系统调用的封装,也可以纯计算(如字符串处理)
例子openreadwriteforkexecvefopenfreadprintfmallocstrlen
来源Linux 内核提供libc(如 glibc)或其他库提供
可移植性依赖操作系统(Linux 特有)标准库函数跨平台(如 ANSI C)

✅ 总结一句话:

系统调用是“内核提供的最小功能单元”,库函数是“程序员友好的封装或扩展”。
写系统程序时,优先用库函数(更简单、更安全),需要精细控制或性能时再直接用系统调用

2.open和close函数

所有函数都可以通过查看手册的方式进行学习,不过难度比较大,这里贴出打开手册的方法,供有志者学习。

man 2 open

open 函数


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;
}

http://www.dtcms.com/a/315008.html

相关文章:

  • 基于Springboot+Mybatis+thymeleaf的个人博客系统的设计与实现
  • EXCEL删除数据透视表
  • 洛谷 P3373 【模板】线段树 2- 普及+/提高
  • C# 类型
  • 基于PSO-NSGAIII混合优化的生产调度算法matlab仿真,输出甘特图,对比PSO和NSGAIII
  • traefik网关鉴权中间件转发multipart/form-data请求的multipart: NextPart: EOF问题
  • 09 Linux基础(8.4)
  • (一)vue3项目初始化(create-vue)
  • 构建属于自己的第一个 MCP 服务器:初学者教程
  • web:ts的字符串string和String
  • Flutter简单讲解
  • ctfshow:pwn85(高级ROP 64 位 Partial-RELRO)、pwn141
  • 内网应用如何实现外网访问?常见方案和简单便捷通用方法步骤
  • SpringBoot格式化数据库表格字段时间戳
  • 华莱士“武”动新章:武林外传IP赋能,开启品牌破圈之旅!
  • XXE漏洞原理及利用
  • VBA-Excel图片下载到本地文件夹
  • 机器学习05——正则化与逻辑回归
  • 大模型LLM介绍
  • 《 java 随想录》| LeetCode二叉树高频算法题
  • WPS2025(官方版)下载与安装教程
  • 一命速通Docker安装+镜像源配置
  • 【node】如何开发一个生成token的接口
  • Disruptor核心:Sequencer解析
  • 软件打包前进行文件去重
  • 正则表达式全解析:从基础到实战(附 Python re 模块用法)
  • 基于知识图谱增强的RAG系统阅读笔记(二)向量相似性搜索与混合搜索
  • ESDocValues机制
  • GEM_ 迈向通用LLM智能体训练新纪元
  • Java 后端 + JavaScript 前端 实现按钮级别权限控制的解决方案