
提示文章文章目录前言一、背景二、2.1 创建src_file.txt文件2.2 编写题目代码三、新的理解3.1 Linux的open函数和fopen函数有什么区别 核心区别概览 1. 来源与定位不同 4. 错误处理方式 5. 适用场景总结3.2 linux应用编程可以使用fopen函数吗 结论Linux 应用编程完全可以用 fopen 为什么 Linux 应用程序可以用 fopen1. glibc 是 Linux 默认的 C 标准库2. fopen 提供缓冲 I/O性能通常更好3. 跨平台性更好 什么时候应该用 open 而不是 fopen 实际开发中的常见组合方式 简单判断方法3.3 为什么linux应用编程的时候加上头文件就能使用open函数呢这个头文件是怎么进去linux系统的 1. 为什么加上头文件就能用 open 2. open 的真正实现在哪里 3. 那头文件是怎么“进入” Linux 系统的✔️ glibc 的开发包libc6-dev / glibc-devel 4. 为什么 Linux 应用程序能直接调用系统调用 5. 整体流程总结非常关键 如果你愿意我可以继续解释总结前言前期疑问本文目标一、背景最近在看【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.4文章第二章文件I/O基础。针对提到的简单测试题如下简单地编程实战例子(1)打开一个已经存在的文件例如src_file使用只读方式然后打开一个新建文件例如dest_file使用只写方式新建文件的权限设置如下文件所属者拥有读、写、执行权限同组用户与其他用户只有读权限。从src_file文件偏移头部500个字节位置开始读取1Kbyte字节数据然后将读取出来的数据写入到dest_file文件中从文件开头处开始写入1Kbyte字节大小操作完成之后使用close显式关闭所有文件然后退出程序。(2)通过open函数判断文件是否存在例如test_file并将判断结果显示出来。(3)新建一个文件例如new_file新建文件的权限设置为文件所属者拥有读、写、执行权限同组用户与其他用户只有读权限。使用只写方式打开文件将文件前1Kbyte字节数据填充为0x00将下1Kbyte字节数据填充为0xFF操作完成之后显式关闭文件退出程序。(4)打开一个已经存在的文件例如test_file通过lseek函数计算该文件的大小并打印出来。下面记录这个题目的设计二、2.1 创建src_file.txt文件I became what I am today at the age of twelve,on a frigid overcast day in the winter of1975.I remember the precise moment,crouching behind a crumbling mud wall,peeking into the alley near the frozen creek.That was alongtime ago,but its wrong what they say about the past,Ive learned,about how you can bury it.Because the past claws its way out.Looking back now,I realize I have been peeking into that deserted alleyforthe last twenty-six years.One day last summer,myfriendRahim Khan called from Pakistan.He asked me to come see him.Standing in the kitchen with the receiver to my ear,I knew it wasnt just Rahim Khan on the line.It was my past of unatoned sins.After I hung up,I wentfora walk along Spreckels Lake on the northern edge of Golden Gate Park.The early-afternoon sun sparkled on the water where dozens of miniature boats sailed,propelled by a crisp breeze.Then I glanced upandsaw a pair of kites,red withlongblue tails,soaring in the sky.They danced high above the trees on the west end of the park,over the windmills,floating side by side like a pair of eyes looking down on San Francisco,the city I now call Home.And suddenly Hassans voice whispered in my head:For you, a thousand times over.Hassan the harelipped kite runner.I sat on a park bench near a willow tree.I thought about something Rahim Khan said just before he hung up,almost as an after thought.There is a way to be good again.I looked up at those twin kites.I thought about Hassan.Thought about Baba.Ali.Kabul.I thought of the life I had lived until the winter of1975cameandchanged everything.And made me what I am today.When we were children,HassanandI used to climb the poplar trees in the driveway of my fathers houseandannoy our neighbors by reflecting sunlight into their Homes with a shard of mirror.We would sit across from each other on a pair of high branches,our naked feet dangling,our trouser pockets filled with dried mulberriesandwalnuts.We took turns with the mirror as we ate mulberries,pelted each other with them,giggling,laughing;I can still see Hassan up on that tree,sunlight flickering through the leaves on his almost perfectly round face,a face like a Chinese doll chiseled from hardwood:his flat,broad noseandslanting,narrow eyes like bamboo leaves,eyes that looked,depending on the light,gold,green,even sapphire.I can still see his tiny low-set earsandthat pointed stub of a chin,a meaty appendage that looked like it was added as a mere afterthought.And the cleft lip,just left of midline,where the Chinese doll makers instrument may have slipped;orperhaps he had simply grown tiredandcareless.来源追风筝的人2.2 编写题目代码#includestdio.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.h// 读写需要的头文件#includestdint.h// uint8_t头文件#includestring.hintmain(){intfd1open(src_file.txt,O_RDONLY);if(fd1-1){printf(fd1 open err\n);return0;}else{printf(fd1 open success\n);}intfd2open(dst_file.txt,O_RDWR|O_CREAT,S_IRWXU|S_IROTH);if(fd2-1){printf(fd2 open err\n);return0;}else{printf(fd2 open success\n);}uint8_tbuffer[1024]{0};intlen1read(fd1,buffer,1024);if(len1-1){printf(fd1 read err\n);returnlen1;}elseif(len11024){printf(fd1 read less then 1024\n);returnlen1;}elseif(len11024){printf(fd1 read success\n);close(fd1);}intlen2write(fd2,buffer,1024);if(len2-1){printf(fd2 write err\n);returnlen2;}elseif(len21024){printf(fd2 write less then 1024\n);returnlen2;}else{printf(fd2 write success\n);close(fd2);}intfd3open(dst_file.txt,O_RDONLY);if(fd3!-1){printf(fd3 open success, fd3:%d\n,fd3);close(fd3);}intfd4open(test_file.txt,O_WRONLY|O_CREAT,S_IRWXU|S_IROTH);if(fd4-1){printf(fd4 open err\n);}else{printf(fd4 open success\n);}chararray1[1024]{0};memset(array1,0x00,1024);chararray2[1024]{0};memset(array2,0xFF,1024);intlen4write(fd4,array1,1024);if(len41024){printf(fd4 write 0x00 error\n);}intlen5write(fd4,array2,1024);if(len51024){printf(fd5 write 0xFF error\n);}close(fd4);uint8_treadBuffer[100]{0};intfd6open(test_file.txt,O_RDONLY);if(fd6-1){printf(fd6 open err\n);}uint32_ttxtLen0;intfd6Lenread(fd6,readBuffer,100);//printf(fd6Len:%d\n, fd6Len);while(fd6Len!0){txtLenfd6Len;fd6Lenread(fd6,readBuffer,100);//printf(fd6Len:%d\n, fd6Len);}close(fd6);printf(txtLen:%d\n,txtLen);}// 代码错误1.一开始写成了fopen而不是open报错// 没写main报错// memset(array2, 0xFF, 1024);写错应该是memset(array2, 0xFF, 1024);// int fd1 open(./src_file.txt, O_RDONLY);写错应该是open(src_file.txt, O_RDONLY); 相对路径没有写对编译后运行fd1opensuccess fd2opensuccess fd1readsuccess fd2writesuccess fd3opensuccess, fd3:3 fd4opensuccess txtLen:2048test_file.txt和dst_file.txt运行程序后可见。三、新的理解3.1 Linux的open函数和fopen函数有什么区别open 和 fopen 的核心区别在于open 是 Linux/UNIX 的系统调用低级 I/Ofopen 是 C 标准库函数高级 I/O带缓冲。两者在来源、返回值、缓冲机制、适用场景等方面都有明显差异。 核心区别概览维度openfopen来源POSIX 系统调用C 标准库函数返回值int文件描述符 FDFILE*文件流指针缓冲机制无缓冲直接与内核交互有缓冲全缓冲/行缓冲/无缓冲适用范围普通文件、设备文件、socket 等普通文件读写移植性较差UNIX 专用较好跨平台读写方式read/writefread/fwrite、fprintf/fscanf 等错误返回-1NULL 1. 来源与定位不同open属于 Linux/UNIX 的系统调用直接进入内核返回文件描述符。适合需要底层控制的场景如设备文件、socket、管道等。fopen属于 ANSI C 标准库跨平台返回 FILE*内部最终也会调用 open。更适合普通文件的读写。缓冲机制差异open无缓冲每次 read/write 都是系统调用开销大但可控性强。适合实时性要求高或需要精确控制写入时机的场景。fopen有缓冲默认使用缓冲区可 setvbuf 调整减少系统调用次数提高效率。适合常规文件读写。使用方式与功能差异open使用 flags如 O_RDONLY、O_WRONLY、O_CREAT 等控制行为读写使用 read/write偏移用 lseek。更灵活但更底层。fopen使用字符串模式“r”、“w”、“a”、“rb” 等读写使用 fread/fwrite 或 fprintf/fscanf。更易用但控制力较弱。 4. 错误处理方式open失败返回 -1通过 errno 获取错误码。 Bfopen失败返回 NULL同样通过 errno 获取错误码。 B 5. 适用场景总结适合用 open 的场景需要操作设备文件、socket、管道需要无缓冲 I/O需要精确控制文件权限、flags嵌入式、系统编程适合用 fopen 的场景普通文件读写需要格式化 I/Oprintf 系列跨平台程序需要缓冲提高性能如果你现在正在写 Linux 程序我可以帮你判断你的具体场景更适合 open 还是 fopen你的使用场景是什么样的3.2 linux应用编程可以使用fopen函数吗可以使用而且在大多数Linux 应用层程序里fopen反而是更常见、更方便的选择。关键在于你写的是什么类型的程序、对 I/O 的控制要求有多细。 结论Linux 应用编程完全可以用fopenfopen属于C 标准库glibcLinux 上的 glibc 完整支持它。绝大多数 Linux 应用程序包括很多开源项目都大量使用fopen、fread、fprintf等高级 I/O 接口。它内部最终还是会调用open所以在 Linux 上使用它没有任何问题。 为什么 Linux 应用程序可以用fopen1. glibc 是 Linux 默认的 C 标准库Linux 上的 glibc 完整实现了 ANSI C 标准fopen是其中的核心接口之一。2.fopen提供缓冲 I/O性能通常更好fopen使用用户态缓冲区减少系统调用次数对普通文件读写非常高效支持格式化 I/Ofprintf、fscanf3. 跨平台性更好如果你的程序未来可能移植到 Windows、macOS、BSDfopen会更通用。 什么时候应该用open而不是fopen在 Linux 应用编程中以下情况更适合open需要操作设备文件如/dev/ttyS0、/dev/null需要无缓冲 I/O实时性要求高需要使用文件锁、O_NONBLOCK、O_DIRECT、O_SYNC等高级 flags需要与select/poll/epoll配合它们只接受文件描述符 FD需要精确控制权限如open(path, O_CREAT, 0644)这些是系统编程范畴fopen做不到。 实际开发中的常见组合方式很多 Linux 程序会混合使用普通文件fopen设备文件、socket、管道open需要 epollopen配置文件、日志文件fopen例如 Nginx、Redis、Git 等项目都大量使用open但也会在某些模块中使用fopen。 简单判断方法如果你写的是程序类型推荐接口普通 Linux 应用程序fopen系统工具、驱动、守护进程open需要 epoll/selectopen需要格式化输出fopen需要跨平台fopen如果你愿意说一下你正在写的是什么类型的 Linux 程序我可以帮你判断应该优先用哪一套接口。3.3 为什么linux应用编程的时候加上头文件就能使用open函数呢这个头文件是怎么进去linux系统的在 Linux 应用编程里只要你#include fcntl.h或#include unistd.h就能使用open。这背后不是“神奇地就能用”而是 Linux 系统和 C 语言工具链共同构建的一套完整机制。理解它需要把头文件、库文件、系统调用、内核接口这几个层次区分开。 1. 为什么加上头文件就能用open头文件本身不提供函数实现它只提供函数声明告诉编译器函数长什么样宏定义如O_RDONLY、O_CREAT常量、结构体定义例如fcntl.h里有intopen(constchar*pathname,intflags,...);编译器看到这个声明后就知道open是一个函数参数类型是什么返回值是什么这样你在代码里写open(a.txt, O_RDONLY)时编译器不会报错。但真正的实现不在头文件里。 2.open的真正实现在哪里open是Linux 内核提供的系统调用不是普通函数。用户程序调用open()时实际发生的是你调用 glibc 中的open()函数这是一个“封装”glibc 通过软中断或syscall指令进入内核内核执行真正的系统调用sys_openat现代 Linux返回文件描述符给用户态也就是说头文件提供声明glibc 提供用户态封装Linux 内核提供真正的实现三者缺一不可。 3. 那头文件是怎么“进入” Linux 系统的头文件不是内核的一部分而是开发环境glibc gcc的一部分。它们来自✔️ glibc 的开发包libc6-dev / glibc-devel在 Linux 系统上安装开发工具链时会安装/usr/include/*.h头文件/usr/lib/libc.soC 标准库/lib/x86_64-linux-gnu/libc.so.6运行时库例如/usr/include/fcntl.h /usr/include/unistd.h这些文件是操作系统发行版提供的开发环境不是内核提供的。 4. 为什么 Linux 应用程序能直接调用系统调用因为 glibc 为每个系统调用都提供了一个“薄封装”。例如 glibc 的open()最终会执行mov eax, __NR_openat syscall这条指令进入内核执行真正的系统调用。所以你调用的是 glibc 的函数glibc 调用的是内核的系统调用头文件只是让编译器知道函数长什么样 5. 整体流程总结非常关键把整个调用链串起来你的代码 ↓ #include fcntl.h —— 提供 open() 的声明 ↓ 编译器gcc —— 知道 open() 的参数和返回值 ↓ 链接器ld —— 把 open() 链接到 glibc 的实现 ↓ glibc —— 通过 syscall 指令调用内核 ↓ Linux 内核 —— 执行真正的系统调用 sys_openat ↓ 返回文件描述符所以你能使用open是因为头文件来自 glibcglibc 提供封装内核提供系统调用编译器和链接器把它们组合起来 如果你愿意我可以继续解释glibc 的open和内核的sys_openat是如何对应的为什么现代 Linux 不再使用sys_open而是openat为什么fopen最终也会调用open如何绕过 glibc 直接调用系统调用你对哪一部分更感兴趣总结未完待续