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

基础IO的介绍(中)

1.重定向下面进入第四个话题先说一下重定向。下面先写一段代码运行后整个结果符合我们的预期。下面基于上述代码来理解新知识我们说过文件描述符本质是数组的下标那么文件描述符对应的分配规则是什么我们已经把文件打开了默认是3。如果关闭0号文件描述符再运行一下此时对应的文件描述符是0。如果关闭1号再运行看到程序什么东西都没有显示出来文件照样可打印。没显示是因为printf要向显示器打印显示器所对应的stdout依赖的文件描述符是1把1文件描述符关了对应的内容就显示不出来了。同样的关闭2号再显示一下会发现fd显示是2。以上现象说明分配规则是从0下标开始寻找最小的没有使用的数组位置它的下标就是新文件的文件描述符。再改一下里面是打开文件和关闭文件向显示器打印对应的内容。下面再调整一下看到运行后消息没有显示因为把1号文件描述符关了很正常但发现把本来应该显示到显示器上的内容写到了文件里这个工作其实叫输出重定向。为什么这样做能完成输出重定向呢因为先把1号文件描述符关了相当于把进程和显示器的关联去了1号文件描述符被腾出来了。紧接着打开一个对应文件时新文件fd是1此时1号下标指向新文件所以写入时写到了文件里。可以通过下图再来理解一下运行时有个进程task_struct每个进程有自己对应的文件描述符表struct file_struct进程里有struct file_struct*files指针指向了对应的文件描述符表。文件描述表里包含了一个数组整个数组叫struct file*fd_array[]下标分别是0、1、2…。进程启动时默认会打开三个流0号指向对应的键盘文件(OS要先描述再组织每个文件是个struct file)1和2号指向对应的显示器文件。我们还打开了一个文件叫log.txt根据前面说的文件描述符分配规则从数组中找最小的没被使用的下标所以打开log.txt后往后给上层的文件表述符罢了。这份代码先做的是close(1)如果1指向的文件没人用对应的文件和数组内容就直接被释放和置空了如果有人用count变为1内容置空此时1号内容被腾出来了。在open时把log.txt地址填到1号文件描述符所对应的数组1号数组下标本来指向显示器文件现在转而指向log.txt文件后续写入代码不知道OS底层把1号下标的内容改了它只认1向1写时变成了向普通文件写。我们上述说的思路将其称为重定向的原理进行重定向时0、1、2这样的数字本身不变本质是在对数组下标里面内容进行修改。下面来进行进一步验证对我们来说我们把文件先关闭再打开一个文件就可以完成上述思路了。但这种方案是不行的但必须要先自己关一次然后紧接着打开一个文件当别人问为啥这样做时需要给别人解释半天。其实系统中有一定系统调用可以快速的不用让我们自己显示关闭而直接打开文件想重定向直接重定向man dup2比如我想进行一个重定向工作不想再close了只想通过接口控制打开文件后调一下重定向后面就重定向了。意思是说初始是这样的现在把3号文件描述符里的指针直接覆盖式的拷贝到1号里面(拷贝前可把1指向文件自动关了拷贝后把3号里面的内容释放等)所以没必要先关闭再打开只要有个接口把文件描述符表里的指针内容做次拷贝就能完成重定向。因此有了一个dup2的接口它有两个参数分别是oldfd和newfd。这有段描述先来看个问题拷贝后最终保留的是哪个文件描述符里的内容(全都是new还是全都是old)描述中说了最终都是oldfd拷贝完只剩oldffd了newfd是要被oldfd覆盖的。当我们要进行输出重定向时把本来要显示到1号文件描述符对应的显示器文件内容直接重定向显示到fd所指向的log.txt文件那么最后剩两个fd的内容还是1的内容呢显然是fd所以fd对应oldfd1对应newfd。那这里说的拷贝是两个文件描述符在拷吗不是了文件描述符是数组下标是常数改不了其实拷贝的是文件描述符在内核当中对应进程的文件描述符表中特定数组下标里的指针内容进行拷贝。下面测试一下发现实现了同样的效果。下面改为追加重定向这就是追加重定向这里可知道追加重定向是打开文件时把选项由清空改为了追加。再再做个测试先回顾一下可用read从fd里把对应的数据读到buf指向的缓冲区count是期望读多少字节ssize_t类似于int表示实际读了多少个。下面来看运行后阻塞了因为当前从0读进程读时发现键盘文件没有就绪下面改一下从标准输入读取转换为从新打开的文件读取运行发现把文件中的内容读出来了这就是输入重定向本应该从键盘文件读转而去指定文件读。下面再看一下因为printf或fprintf这里用的是stdin里面封装的文件描述符是1这里改了指向。目前知道了无论当前用系统调用还是库函数最终消息打印都是符合的。继续看前面说的重定向和命令行上用的是什么关系看下图前面我们自己写过一个shell获取命令行可能有这样的情况。说白了符号都在获取时被整体当字符串读进来了这些符号是整个字符串的一部分符号左侧是指令右侧是要重定向的文件。然后我们定义rdirfilename和rdir方式然后通过Interact后commandline里不会带重定向符号只有命令。下面执行普通命令里面创建子进程里面判断不同情况每次执行完从全局清空一下再进一步完善一下下面来编译看一下现在有个问题我们做了重定向工作后面我们在进行程序替换的时候难道不影响吗目前可以盘点一下了之前说过进程内核数据结构自己的代码和数据引入先描述再组织的概念后我们说每个进程都有自己的task_struct。后来我们又引入了一个数据结构叫mm_struct与此同时引入了简易的页表映射关系还有对应的物理内存。运行一个进程要把进程代码和数据放内存里OS给我们创建对应地址空间pcb通过页表来映射cpu找到进程调度。现在又知道了打开一个文件时要给我们创建struct file对象进程为了维护自己和文件间的对应关系就有了struct files_struct表所以pcb里有struct files_struct*files这样的字段无论是打开文件还是进程和打开文件产生关系的文件描述符数组其中左侧这些都叫内核数据结构当我们打开文件重定向时后面再做了程序替换把新程序的代码和数据替换修改页表和mm_struct部分字段它改的是进程代码和数据部分和文件那边没有关系很明显内存管理和文件操作之间也是一种解耦关系。因此进程历史打开的文件与进行的各种重定向关系都和未来进行程序替换无关程序替换并不影响文件访问。下面再来看个问题先看问题11 vs 2也就是我们重定向工作当我们标准输出和标准错误输出时都打到显示器上了那有什么区别下面写个代码都是往显示器上面打印符合预期。下面再看重定向时发现error message这些消息没被重定向normal message被重定向了因为输出重定向后原来往1里写的会写到normal.log里2没有做重定向所以默认在显示器上继续显示。我们再继续加个指令回车后显示器上什么都没有ll后看到有err.log和normal.log打开后看到了它们的内容。其实上面的指令详细写是这样本来正常和错误消息是混在一起的现在把它们分别重定向到不同文件这样能很好的看出错误消息。那就想把它们弄到一起呢(上面详写下面简写)这写的什么意思像前面就很直观把标准输出的写到normal.log标准输入的写到err.log(1可被省略)。这里代表的是./mytestall.log完成后1已经指向all.log了21代表把1中的内容写到21里面的内容此时是all.log的地址所以此时1和2都指向all.log这样就写到了一个文件如上就是重定向原理和操作后半部分。再看第二个问题如何理解linux中一切皆文件所有操作计算机的动作都是以进程形式进行操作的所有访问文件的操作都是用进程方式访问文件的。进程是OS帮用户完成任务的主要渠道目前我们所有对文件的操作都依赖于进程操作。在系统里一定会存在不同的设备比如键盘、显示器、网卡、磁盘等这里挪列的设备并没有包含内存、cpu这样的设备这里列的设备大部分都是外设。先别说什么一切皆文件磁盘和显示器就是不一样比如属性、操作方法的不一样。但对OS来讲在冯诺依曼体系结构中虽然每一种外设需要的方法在实现上绝对不一样但方法种类上差不多。比如磁盘有读写显示器有读写只不过写方法是有的读方法是空的。我们每一种对应的设备最终给OS管理它都要有对应的描述结构体每种设备都要自己配上自己的读写方法没有的置空也就是每种设备访问方法在实现上一定不一样但在冯诺依曼中每种方法其实可以都提供类似的接口。因为linux下一切皆文件所以OS就说在我看来底层设备包括打开的普通文件都是文件所以未来打开磁盘、显示器等都可以文件方式被系统调用open打开。打开后每个文件都要给它在内核中创建一种数据结构struct file每种设备都有自己的读写方法给每个设备都创建了struct_file。将来进程打开文件后要对文件读写底层设备都不一样怎么做到让它访问不同的设备因此linux内核里又提供了一个结构叫struct operation_func这是一张方法表的数据结构每打开一个文件时如打开磁盘文件时为磁盘文件创建一个方法集对象然后在struct file中包含一些指针指向这个对象里面是函数指针然后把读磁盘的方法和写磁盘方法分别放过去让指针指向底层的方法。比如又打开了显示器就创建一个通用的文件对象然后创建一个方法集然后以同样方法指向OS为了让所有人认为一切皆文件用访问文件的方式访问所有的设备所以进程被创建了进程有了对应的文件描述符表进程pcb指向自己的表然后0、1、2分别指向对应的structfile然后OS专门给我们设计了系统调用。readwrite它里面会传fd通过task_struct找到files再找到fd_array[fd]这样找到了对应的structfile再找到f_ops再找到方法此时系统层面看起来调的是read但下面根据指向不同可通过一个上层接口调不同的方法。其实相当于OS在文件层面封装了一层叫struct file的文件对象文件对象里面有指针指向不同设备操作方法方法上面采用函数指针变相对底下方法汇总上层用的时候不关心设备驱动层方法如何实现这样看到了一切皆文件。在linux系统里把structfile这一层称为VFS叫作虚拟文件系统比如网卡想被打开这个设备要提供自己的读写方法(驱动中规定好的)然后创建对应structfile文件对象然后创建操作的方法指针集然后文件对象指向方法集方法集指向方法这样就把网卡设备纳入到了文件仔细来看这两层其实是多态所有struct file是基类下面是派生类上层指针指向哪个对象就访问哪个对象的方法。2.缓冲区下面先写一段代码这里说一下fwrite和fread差不多第二个参数是要写入块的大小第三个参数是块的个数这个返回值返回的是nmemb的个数比如要写4个字节写10个4字节真正写了40个字节进去这里size_t返回的是10比如要写4个字节写10个4字节写了5个4字节它的返回值就是5。向显示器写入有write接口它可向指定文件流中写入缓冲区最后是缓冲区大小返回值是实际写入的字节的个数。现在代码中上面三个是c语言提供的最后一个是操作系统提供的上三个其实最后都调用了write接口。下面运行一下一切符合预期。下面再做一下重定向一切也都符合预期。下面改一下再次运行再次重定向一下(重定向会对文件做清理)此时发现消息变多了。以上做了这样一些现象首先看个问题带fork后为啥重定向打印出来是7行呢目前还不知道但发现c接口打了两次系统调用打了一次。第三次结尾也有fork但显示器上都各打了一次重定向文件后每个c式接口的输出字符串多打了一次目前感觉到一定和fork有关系。下面谈一下缓冲区把代码改一下make后看到此时结果可以打印。下面把代码/n去掉再运行一下看到程序输出的结果没有了重定向cat后也没有结果。再改一下代码并运行看到可以打出来。同样的待遇用c语言的接口打印字符串带/n的就直接刷出来了不带/n的close后没有结果系统调用接口时发现带不带/n最后消息都能出来。凭什么调系统调用接口先调接口再关闭文件描述符就打出来了凭什么打c语言的时候这上面都没有我们说过我们用的printf/fprintf/fwrite等都是c式的接口底层一定调用了write接口截然这样打字符串时一定要交给对应的write那应该和write效果一样呀但事实并没有。曾经进度条那里不带\n看到即使把代码往显示器上打但消息不会立刻刷新其实这回消息已经被写入了只不过写入到了缓冲区。今天调用printf、fprintf等时照样没带\n照样往显示器上打印走到close前已把数据写入到了系统当中了(缓冲区)只不过这个缓冲区一定不在OS内部不是系统级别的缓冲区。为啥这样说呢这有操作系统里面有写的文件structfile文件有自己对应的缓冲区外面有磁盘、显示器等。进程pcb通过文件描述符找到文件把数据写到自己文件缓冲区中然后文件缓冲区的内容刷到磁盘里(文件一定要提供OS级别的缓冲区)。当调用printf、fprintf、fwrite这样的接口时一定不是把数据拷贝到文件的内核缓冲区中若拷贝进去了后面调close时能找到对应文件然后把文件缓冲区数据刷到磁盘里最后是可以看到结果的但事实上并没有。为啥write能看到呢因为写入的字符串通过write这样的系统调用接口直接写到了系统缓冲区里后面调close关这个文件时就把缓冲区的内容刷新出来了这就是系统接口能看到的原因。但c接口底层调了write把参数给write并且把文件拷到内核缓冲区了数据注定会被刷新出来可事实没有。因为c/c里的缓冲区不在系统内部这里说的缓冲区是在语言层的(c语言它会给我们提供一个缓冲区)这个缓冲区是用户级缓冲区调的c式的这批接口并不是把数据直接给了write而是把这些数据写到了c语言的缓冲区里当我们在合适的时候如遇到强制刷新、\n才会去调write接口把这个缓冲区的内容写到系统缓冲区再刷新所以当我们直接fprintf等时我们这个消息是在上层的c语言缓冲区调close后把1号文件的描述符关了进程退出时想调write刷新但已经关了所以刷不出来了。再回头看看第一次都打印了第二次只有write打印。第一次为啥都打印出来了像显示器的文件的刷新方案是行刷新所以printf执行完会立即遇到\n时候将数据进行刷新这里刷新的本质目前就是将数据通过1write写入到内核中。基于上述再继续以前说exit和_exit是有区别的以前说_exit更强势不会让系统数据刷新exit会帮我们直接把数据刷新出来。现在就知道exit是C语言的接口它退的时候能看到语言提供的缓冲区所以能把数据刷出来。_exit是系统调用它不知道上面有语言的缓冲区退的时候直接关文件描述符和释放进程没有刷新的机会。目前我们认为只要把数据通过系统调用接口写到了系统内部OS会帮我们把数据刷到硬件。基于这个来谈谈语言这一层c语言在语言层给我们封装了缓冲区我们调printf、fprintf等接口可对缓冲区进行写入写完后再调用合适的write接口把数据写到OS(如以前调fflush就是调write把数据写OS内部)。说过上述再来引入下面的问题1.缓冲区刷新问题语言层把数据写到缓冲区里就可以进行刷新了可该怎么刷新呢这里说的刷新策略是应用层的刷新策略OS也要把自己的缓冲区数据刷到磁盘等但我们不管这是OS内部自己做的。我们只需要管好上面的我们要清楚离用户近的语言层的刷新策略这样才理解加和不加\n等为啥会出现不同的表现。所谓缓冲区刷新策略问题一般就三种a.无缓冲。b.行缓冲。c.全缓冲。所谓的无缓冲说白了是一种立即写入模式就是直接刷新相当于对应的printf接口把数据写到缓冲区里别管什么刷新策略写完后直接调write接口写到内核里。所谓的行缓冲首先调c接口把数据写到缓冲区c库就看看有没有\n若缓冲区遇到了第一个\n再全刷新出来。也就是它的意思是缓冲区不刷新直到碰到\n。所谓的全缓冲不管写入什么都不管直到把缓冲区写满才调一次write把数据刷新到内核里也就是缓冲区满了才刷新。下面整体做一个说明我们语言层首先调fprintf/fwrite等然后将内容写到c缓冲区里再根据一定策略调write通过write写OS中。我们凭什么调write取决于缓冲区的刷新策略。2.周边一些补充问题我们以前用过一个接口叫fflush它底层一定封装了write。下面问题来了我们的刷新策略默认向显示器打印时是行刷新的。因为显示器是给人看的人的阅读习惯默认是以行为单位的显示器要立即看到。我们一般向文件写入时采用的刷新策略是全缓冲因为文件内容不需要用户立马来查看所以为了提高效率就把对应的缓冲区写满然后调write向文件写入。一般c语言上缓冲区刷新还有一种时机是当进程退出的时候也会刷新。那为什么要有这个缓冲区c为啥提供呢这要分两方面谈1.效率层面的价值。2.语言设计上。a.解决效率问题——用户的效率问题(比如快递的例子快递站相当于用户缓冲区)。初学c语言时我们把printf这样的接口叫格式化输入输出接口我们向显示器打印的是字符1、2、3这样但我们调printf %d后面写的是整数变量所以进行格式化输入输出调系统调用前先把数据格式化形成字符串到一个区域里如写入时有这样的转化转完后传给write。因此缓冲区的第二个意义是b.配合格式化。我们一般把c缓冲区叫文件流因为我们调printf/fprintf这样的接口不断向缓冲区里写入内容然后把缓冲区的内容通过系统调用刷新到设备中。有数据不断向缓冲区写和拿出缓冲区存在有点像河道有进有出所以有了流的概念称为文件流。下面说第三个问题这个缓冲区在哪里当我们在fflush一个流的时候它里面只传了一个FILE*的接口发现c语言中文件操作绕不开FILE。我们说过FILE是一个struct结构体它里面要封装对应的fd它里面还有对应打开文件的缓冲区字段和维护信息可理解为FILE结构体里包含了对应的缓冲区。比如当我们调fprintf(stdout “hello world\n”)时stdout是个FILE*的它里面包含文件描述符内部也要维护缓冲区当我们调fprintf时是把hello world拷贝到自己的缓冲区再根据\n和满写规律设定看是否把缓冲区数据刷出去调write方法。所以我如果在c语言中打开了10个文件那有几个语言级别的缓冲区10个每个文件都有自己的缓冲区也有10个文件描述符每个文件配一个自己的语言层缓冲区每个文件都把自己语言层缓冲区通过它自己的文件描述符刷新到我们对应的磁盘上。那这个FILE对象属于用户还是属于操作系统呢FILE对象属于用户因为语言都属于用户层所以缓冲区也是属于用户的。那我们学习fopen时为啥返回的是FILE*呢fopen是c标准库(libc.so)给我们提供的接口fopen打开文件底层调open在内核层面帮助我们建立内核级别的文件对象并且拿到文件描述符。同时在语言层给我们malloc(FILE)所以返回的是FILE*一旦malloc出FILE这个FILE里封装了对应的文件描述符和继续malloc出语言级的缓冲区。继续来看make运行时打印4条消息重定向先清空文件打印的就是7条为什么呢我们整个打印过程是如我们调了fprintf它里面对应的stdout输出字符串然后在缓冲区把数据写进去了然后调write接口把数据刷新出去到OS。OS里有对应的进程pcb文件描述符表文件对象然后通过文件描述符表把数据写到文件级的缓冲区。其中我们打印时默认带了\n所以是行刷新见\n数据就调write写到内核进而刷新到磁盘。一旦我们重定向了本来向显示器打印变成了向文件打印我们的缓冲策略变成了缓冲这样遇到\n不在刷新而是写缓冲区被写满才刷新。下面举个例子每次sleep(2)因为缓冲区没满即使sleep了代码也没有写到文件。write后sleep(5)write后把数据写到了磁盘过5秒进程退出做强制刷新上面消息才能被刷出说明6秒c接口以及跑完了只不过数据没刷新到系统里write接口直接写OS所以能刷出来当进程退出时全刷出来。最后带上fork进程退出前执行fork一旦执行fork OS要创建子进程对应父子进程的代码是共享的数据会以写时拷贝方式被各自私有一份。我们把数据从缓冲区刷新到系统中的时候这个缓冲区是用户级的也就属于进程的一部分(相当于malloc出的堆空间)。fork后父进程想刷新本质也是对缓冲区的清空清空的本质就是写入。OS在对这段缓冲区操作时发生写时拷贝父子进程对这段缓冲区各自私有一份所以发现最后被刷新了2份。那为什么同样有fork不重定向时打1份因为没重定向时刷新方案是行缓冲然后调c接口时消息被刷到了显示器上fork后没什么刷新动作所以只打了一份。3.模拟实现下面来模拟实现一下来更好的能理解上述理论(为了不和库冲突模拟的前面带-)今天模拟实现一下这样一些接口现在依次实现提供的一个个方法_fopen要开打对应的文件它里面要封装open的。open的第一个参数是filename第二个是方式(这里flsg只实现war)然后分别比较是w说明它想写文件不存在就craet默认清空a和w类似不同的打开方式用不同的调用-1说明文件根本没有打开当语句走到-1判断后面说明文件打开成功了。此时需要创建出对应的文件对象让别人来用走到下面说明malloc也成功了设置好返回一个_FILE*上面就是fopen大概做的事情。下面看_fclose关掉对应的文件描述符然后释放至此有了文件打开和关闭了。再看_fwrite下面来测试一下经过这样的封装我们就不用关心系统了若今天我们是在linux上c语言中把linux的接口用这样方式封装一遍OS不一样系统访问文件的接口肯定不一样c语言中在windows下访问文件的接口封装一下mac上也这样实现一份。然后把三分代码以条件编译形式都放到c语言里然后分别裁为适合三个系统的库然后三个系统上分别保留上层适合自己下层的接口从此一个人在不同系统中能用同样的c接口这就叫c语言具有跨平台性。下面把缓冲区添加进去(简易版)有对应的输入和输出缓冲区这主要用一下输出缓冲区。我们以前说过printf是有缓冲区的那scanf输入有没有缓冲区有的比如键盘输入123其实我们输的是123字符系统调用read读的是字符串‘123’字符串。要读字符串把它先保存到了接收缓冲区inbufferscanf后接收时用的整数变量然后把字符串转整数拷贝到对应的变量。系统调用上没有所谓的类型所有东西再它看来都是字符串只管把buf读或写格式化是语言的事情。如printf打一个整数首先做的是把整数转为字符串拷贝到outbuf里最后经过write接口写出去。文件有最终对应的刷新方式这里人为的写flag还有pos用来衡量缓冲区中的有效字符再补充一下.c这样fwrite要把char*s吓到缓冲区。下面再来测试一下看到按行刷新的方式不断地向我们文件内容写入。再改为全刷新方式测试一下进程退出时文件里照样什么都没有因为我们代码中少了进程退出时代码强制刷新的逻辑所以关闭文件前检查一下缓冲区中有没有数据若有就刷出去这样就看到进程退出时文件刷新了。那缓冲区的意义是什么调用系统调用把数据拷给OS是有时间成本的要传10次就调系统调用10次效率太低。若此时攒一大批接口数据放buffer里最后统一再刷新此时1次和OS交互就可刷新很多数据这样可让我们用c语言接口变的更快调完c接口数据放缓冲区函数返回。4.完整代码//main.c #include Mystdio.h #define myfile test.txt int main() { _FILE *fp _fopen(myfile, a); if(fp NULL) return 1; const char *msg hello world\n; int cnt 10; while (cnt){ _fwrite(fp, msg, strlen(msg)); sleep(1); cnt--; } _fflush(fp); _fclose(fp); return 0; }//Mystdio.h //#pragma once #ifndef __MYSTDIO_H__ #define __MYSTDIO_H__ #include string.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h #include assert.h #define SIZE 1024 #define FLUSH_NOW 1 #define FLUSH_LINE 2 #define FLUSH_ALL 4 typedef struct IO_FILE{ int fileno; int flag; //char inbuffer[SIZE]; //int in_pos; char outbuffer[SIZE]; //用一下这个 int out_pos; }_FILE; _FILE* _fopen(const char*filename, const char *flag); int _fwrite(_FILE *fp, const char *s, int len); void _fclose(_FILE *fp); void _fflush(_FILE *fp); #endif//Mystdio.c #include Mystdio.h #define FILE_MODE 0666 // w a r _FILE* _fopen(const char*filename, const char *flag) { assert(filename); assert(flag); int f 0; int fd -1; if(strcmp(flag, w) 0){ f (O_CREAT|O_WRONLY|O_TRUNC); fd open(filename, f, FILE_MODE); } else if (strcmp(flag, a) 0){ f (O_CREAT|O_WRONLY|O_APPEND); fd open(filename, f, FILE_MODE); } else if (strcmp(flag, r) 0){ f O_RDONLY; fd open(filename, f); } else return NULL; if (fd -1) return NULL; _FILE *fp (_FILE*)malloc(sizeof(_FILE)); if (fp NULL) return NULL; fp-fileno fd; //fp-flag FLUSH_LINE; fp-flag FLUSH_ALL; fp-out_pos 0; return fp; } int _fwrite(_FILE *fp, const char *s, int len) { //abcd\n memcpy(fp-outbuffer[fp-out_pos], s, len); fp-out_pos len; if(fp-flagFLUSH_NOW) { write(fp-fileno, fp-outbuffer, fp-out_pos); fp-out_pos 0; } else if(fp-flagFLUSH_LINE) { if(fp-outbuffer[fp-out_pos-1] \n){ write(fp-fileno, fp-outbuffer, fp-out_pos); fp-out_pos 0; } } else if(fp-flagFLUSH_ALL) { if(fp-out_pos SIZE){ write(fp-fileno, fp-outbuffer, fp-out_pos); fp-out_pos 0; } } return len; } void _fflush(_FILE* fp) { if(fp-out_pos 0){ write(fp-fileno, fp-outbuffer, fp-out_pos); fp-out_pos 0; } } void _fclose(_FILE *fp) { if (fp NULL) return; close(fp-fileno); free(fp); }

相关文章:

基础IO的介绍(中)

1.重定向下面进入第四个话题,先说一下重定向。下面先写一段代码:运行后整个结果符合我们的预期。下面基于上述代码来理解新知识:我们说过文件描述符本质是数组的下标,那么文件描述符对应的分配规则是什么?我们已经把文…...

Beyond Compare 5终极激活指南:深入解析密钥生成与RSA加密技术

Beyond Compare 5终极激活指南:深入解析密钥生成与RSA加密技术 【免费下载链接】BCompare_Keygen Keygen for BCompare 5 项目地址: https://gitcode.com/gh_mirrors/bc/BCompare_Keygen Beyond Compare 5作为业界领先的文件对比工具,其强大的文件…...

从视频流量到搜索权重:一份素材如何驱动多平台内容复用

在2025年之前,许多SaaS团队将内容策略的重心放在视频平台。YouTube教程、TikTok快速演示、LinkedIn行业洞察——这些内容确实带来了可观的观看量和互动。但到了2026年,一个越来越明显的问题浮现出来:视频流量虽然即时,却像流水一样…...

告别复杂配置!ERNIE-4.5-0.3B-PT模型vLLM部署与Chainlit调用详解

告别复杂配置!ERNIE-4.5-0.3B-PT模型vLLM部署与Chainlit调用详解 1. 快速部署ERNIE-4.5-0.3B-PT模型 ERNIE-4.5-0.3B-PT是百度推出的轻量级文本生成模型,基于专家混合(MoE)架构设计,具有300亿参数但仅激活0.3亿参数。使用vLLM部署可以大幅提…...

3步完整指南:使用OpenCore Legacy Patcher让老旧Mac焕发新生

3步完整指南:使用OpenCore Legacy Patcher让老旧Mac焕发新生 【免费下载链接】OpenCore-Legacy-Patcher Experience macOS just like before 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 你是否有一台被苹果官方抛弃的老款Ma…...

激光雕刻入门指南:5分钟掌握LaserGRBL完整使用技巧

激光雕刻入门指南:5分钟掌握LaserGRBL完整使用技巧 【免费下载链接】LaserGRBL Laser optimized GUI for GRBL 项目地址: https://gitcode.com/gh_mirrors/la/LaserGRBL 想要轻松操控激光雕刻机却担心操作复杂?LaserGRBL激光雕刻软件为你提供了完…...

3分钟零门槛安装:Axure RP中文语言包全面解析

3分钟零门槛安装:Axure RP中文语言包全面解析 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界…...

万事开头难,读懂屯卦的智慧,你就知道创业、求职、成家该怎么走

开头难,不是吓你,是规律你有没有发现,人生最难的事,往往都是“第一次”?第一次创业,第一次找工作,第一次生孩子,第一次写书,第一次开店……每一件事在开始的时候&#xf…...

用 Microsoft Agent Framework 构建 SubAgent(Multi-Agent)嵌

本文能帮你解决什么? 1. 搞懂FastAPI异步(async/await)到底在什么场景下能真正提升性能。 2. 掌握在FastAPI中正确使用多线程处理CPU密集型任务的方法。 3. 避开常见的坑(比如阻塞操作、数据库连接池耗尽、GIL限制)。 …...

艾尔登法环调试工具:探索交界地的终极调试指南

艾尔登法环调试工具:探索交界地的终极调试指南 【免费下载链接】Elden-Ring-Debug-Tool Debug tool for Elden Ring modding 项目地址: https://gitcode.com/gh_mirrors/el/Elden-Ring-Debug-Tool 在《艾尔登法环》的广阔世界中,褪色者们常常渴望…...

STM32 Bootloader分区实战:12K空间如何优化配置(附Keil生成bin/hex命令)

STM32 Bootloader分区实战:12K空间优化配置与Keil生成技巧 在嵌入式开发领域,Bootloader设计往往是产品稳定性和可维护性的第一道门槛。面对有限的Flash资源,如何合理分配Bootloader与应用程序(APP)的空间,成为每个STM32开发者必…...

多模态入门新选择:ViLT模型实战,从文本处理到图像理解的统一Transformer玩法

多模态入门新选择:ViLT模型实战,从文本处理到图像理解的统一Transformer玩法 当你第一次听说多模态学习时,脑海中可能会浮现出复杂的双流架构、繁琐的区域特征提取,以及让人望而生畏的计算资源需求。这正是大多数Vision-and-Langu…...

海康工业相机SDK取图性能优化:从MV_CC_GetOneFrameTimeout到MV_CC_GetImageBuffer的实战避坑

海康工业相机SDK取图性能优化实战:从MV_CC_GetOneFrameTimeout到MV_CC_GetImageBuffer的深度解析 在工业视觉系统的开发中,持续稳定的图像采集是保证检测精度和生产效率的关键。许多开发者在使用海康威视工业相机SDK时,往往会从最直观的MV_CC…...

单片机开发者必看:从蓝桥杯真题学电源电路设计(BUCK电路详解版)

单片机开发者必看:从蓝桥杯真题学电源电路设计(BUCK电路详解版) 在电子设计竞赛和实际项目开发中,电源电路的设计往往是决定系统稳定性的关键因素。作为一名长期参与蓝桥杯赛事指导的工程师,我发现许多参赛者在BUCK电路…...

Altium Designer实战:从零开始设计STM32最小系统PCB

1. 准备工作与环境搭建 在开始设计STM32最小系统PCB之前,我们需要做好充分的准备工作。首先确保你的电脑上已经安装了Altium Designer软件,建议使用较新的版本(如AD20或更高),因为新版本在稳定性和功能上都有显著提升…...

从SAC到HIL-SERL:拆解LeRobot中强化学习算法的工程化集成与调试

从SAC到HIL-SERL:拆解LeRobot中强化学习算法的工程化集成与调试 在具身智能领域,强化学习算法的落地应用一直面临着理论与工程之间的巨大鸿沟。LeRobot框架通过HIL-SERL(Human-In-the-Loop Sample-Efficient Reinforcement Learning&#xff…...

Hive视图实战:从创建到删除,一个完整的学生信息视图案例(附避坑点)

Hive视图实战:从创建到删除,一个完整的学生信息视图案例(附避坑点) 在数据分析的日常工作中,我们常常需要处理结构复杂的数据表。想象一下这样的场景:你手头有一张包含学生详细信息的大表,每次查…...

Simulink | 【开源】基于自适应惯量阻尼的虚拟同步发电机(VSG)并网稳定性仿真

1. 虚拟同步发电机(VSG)技术背景 新能源发电占比越来越高,风电、光伏这些"看天吃饭"的电源接入电网后,传统电力系统遇到了新挑战。打个比方,原来电网就像个稳重的大胖子(同步发电机自带惯性),现…...

Multisim仿真NE555驱动NMOS总报错?手把手教你修改仿真参数搞定PWM调光电路

Multisim仿真NE555驱动NMOS报错全解析:从参数调优到实战调光 当你在Multisim中搭建NE555 PWM调光电路时,是否遇到过一接上NMOS就仿真崩溃的尴尬?那个刺眼的"瞬态分析无法收敛"报错窗口,仿佛在嘲笑你连基础电路都搞不定。…...

企业级Vue3项目实战:基于Vite的高效前端工程化配置指南

1. 为什么选择ViteVue3构建企业级项目 如果你最近关注前端技术动态,应该会发现ViteVue3的组合越来越频繁出现在各大企业的技术栈中。我去年负责过一个大型后台管理系统的重构,当时从WebpackVue2迁移到ViteVue3后,开发体验提升非常明显——冷启…...

10分钟释放100GB空间:AntiDupl重复图片清理终极指南

10分钟释放100GB空间:AntiDupl重复图片清理终极指南 【免费下载链接】AntiDupl A program to search similar and defect pictures on the disk 项目地址: https://gitcode.com/gh_mirrors/an/AntiDupl 你是否曾经因为硬盘空间不足而烦恼?是否在整…...

别再手写Verilog了!用Simulink HDL Coder快速搭建FPGA原型(附避坑指南)

从算法模型到硬件实现:Simulink HDL Coder高效FPGA开发实战 在数字信号处理和通信系统开发领域,FPGA因其并行计算能力和可重构特性成为算法加速的理想平台。然而,传统手写Verilog/VHDL的开发模式存在几个显著痛点:开发周期长&…...

Llama-3.2V-11B-cot部署案例:Docker镜像免配置运行图文推理API服务

Llama-3.2V-11B-cot部署案例:Docker镜像免配置运行图文推理API服务 想体验一个能看懂图片、还能像人一样一步步思考的AI吗?今天要介绍的 Llama-3.2V-11B-cot 就是这样一个模型。它不仅能识别图片里的内容,还能把思考过程一步步拆解给你看&am…...

如何在.NET应用中轻松实现PDF打印?PDFtoPrinter完整实战指南

如何在.NET应用中轻松实现PDF打印?PDFtoPrinter完整实战指南 【免费下载链接】PDFtoPrinter .Net Wrapper over PDFtoPrinter util allows to print PDF files. 项目地址: https://gitcode.com/gh_mirrors/pd/PDFtoPrinter 你是否曾为在.NET应用中集成PDF打…...

010、AI硬件复兴:从NPU到专用芯片的创业路径

010、AI硬件复兴:从NPU到专用芯片的创业路径 文章目录010、AI硬件复兴:从NPU到专用芯片的创业路径一、从一次深夜调试说起二、NPU的“夹层生存”现状三、专用芯片的“场景深潜”四、创业路径上的四个暗礁五、给务实主义者的建议六、写在最后一、从一次深…...

如何将微信聊天记录永久保存并深度分析?WeChatMsg终极解决方案

如何将微信聊天记录永久保存并深度分析?WeChatMsg终极解决方案 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/…...

Windows任务栏定制神器:7+ Taskbar Tweaker让你的桌面效率翻倍

Windows任务栏定制神器:7 Taskbar Tweaker让你的桌面效率翻倍 【免费下载链接】7-Taskbar-Tweaker A Windows taskbar customization tool for Windows 7, Windows 8, and Windows 10 项目地址: https://gitcode.com/gh_mirrors/7t/7-Taskbar-Tweaker 你是否…...

3个实战案例:用AKShare快速构建Python金融数据分析系统

3个实战案例:用AKShare快速构建Python金融数据分析系统 【免费下载链接】akshare AKShare is an elegant and simple financial data interface library for Python, built for human beings! 开源财经数据接口库 项目地址: https://gitcode.com/gh_mirrors/aks/a…...

忍者像素绘卷:天界画坊C++高性能推理引擎封装实战

忍者像素绘卷:天界画坊C高性能推理引擎封装实战 1. 为什么需要高性能推理引擎 在游戏开发和工业软件领域,实时图像生成和处理对性能要求极高。传统的Python推理框架虽然易用,但在延迟敏感场景下往往力不从心。这就是我们需要用C打造专属推理…...

Linux学习日常3

1、cd命令 更改当前目录 英文全称change directory ,结构 cd [文件名]2、pwd命令 语法 pwd无选项无参数直接输入 验证当前目录 英文全称print work directory3、绝对路径写法 命令示例:cd /home/itheima/Desktop 相对路径写法 命令示例:cd De…...