从一道面试题分析Linux进程+IO缓冲区机制

下面的程序打印出多少个“*“    (小弟今年遇到的腾讯一面面试题,据说其他公司的面试题中也有这个题目)

#include <unistd.h>
#include <stdio.h>

int main()
{
	for (int i = 0; i < 2; i++)
	{
		fork();
		printf("*");
	}
	
	return 0;
}


 

答案打印出8个*,而不是6个。

 

 


分析:


如果不考虑IO机制,一般可能会想,i = 0时,fork后有两个进程,打印两个*,i=1,时有四个进程,再打印4个*。所以一共6个。其实不是这样的。

        进程机制:
         fork分叉后子进程似乎父进程的副本,子进程会复制父进程的数据空间、堆和栈。而共享代码段。

         C语言标准IO的缓冲机制:
         C语言的标准IO是带缓冲的,一般调用printf后,并不是立即把要打印的内容立即打印在控制台界面上,而是输出到一个缓冲区,对应标准输出的叫标准输出缓冲,对应标准出错的叫标准出错缓冲,当然还有标准输入缓冲。

         缓冲机制:
         缓冲机制一般分为:全缓冲、行缓冲、无缓冲。

  •          全缓冲:缓冲区满了以后,才发生真正的IO。我们通常用的磁盘文件IO就是这样的。当然你可以调用flush类函数强制刷新缓冲。
  •          行缓冲:缓冲区满了以后或者缓冲区收到一个换行符(表示已输入或输出一行),后才发生真正的IO,比如标准输出和标准输入默认的缓冲机制就是行缓冲。(行缓冲还有一些规则,参考APUE)
  •          无缓冲:立即发生IO,通常标准出错是不带缓冲的。所以建议用输出信息来调试程序时,最后用标准出错IO,以免调试信息延迟输出。

        上面三种我们都能用flush和关闭文件之类的函数强制刷新缓冲。C语言还提供了接口让我们改变默认的缓冲机制,以及缓冲区的大小。

       回到我们的问题,根据linux进程机制,题目中的标准输出文件描叙符也是会被子进程复制的。所以fork后所有的进程共享一个控制台窗口(这个解释好像有点多余,看不懂跳过)。IO的缓冲区是用malloc申请的,是属于堆区,所以也是子进程要从父进程复制的。那么i= 0时,有两个进程,他们各自输出了一个*,而且由于标准输出默认采用的是行缓冲机制,所以此时,它们只是各自把一个*复制到了标准输出的缓冲区,没有打印到c控制台窗口上。 接下来是关键时刻,接下来 i = 1,再一个运行到fork()后,子进程复制父进程的堆区,所以后面出生的子进程也和父进程有着一样的标准输出缓冲区,而且标准输出缓冲区中同样也有着一个*。然后运行大printf语句,所有的进程都又各自向自己的标准输出缓冲区输送了一个*。  然后 i=2,程序结束了,标准输出缓冲区的内容打印到控制台窗口上,你就看到几个*了。
        好的,我们现在统计一下。每个进程都一个输出了几个*。
        我们把进程按照出生的先后分为两拨,第一波是i = 0时就出现的,包括主线程和第一个字进程,在整个循环中他们向自己的标准输出缓冲区都输出了两个*。
        第二波是在i = 1时产生的两个子进程,它们各自像自己的标准输出缓冲区输出了一个*,但是由于它们从父进程的缓冲区里复制了一个*,所以它们的标准输出缓冲区中都有两个*。
        最后程序结束,关闭标准输出文件时,IO发生,一共向控制台窗口打印了:两个x两个 + 两个x两个 = 8个 *。

运行结果截图
185719r7g3jpvpvgba05ja.png 

如果把程序中的printf语句中加上一个\n符号,改为printf("*\n"),就是下面的结果,打印出了6个*。



PS:不同平台默认的缓冲机制可能不一样,上面总结的是一般情况,我当初在win7上和ubuntu12.04上测试的结果是相符的。

如果分析中有错误,欢迎指正!。



       去腾讯面试的前一两天,我去隔壁宿舍听到他说起这个问题。由于去他们宿舍有其他事,我没注意他说的这个题目。后来腾讯一面,面试官居然问我这个问题。我想糟了,大脑一片空白,为什么我当初没注意这个问题呢? 于是我就被腾讯一面刷了。
后来想想这个题目不难啊。这个题目考的就是linux进程分叉和IO缓冲机制,我以前看apue时总结过好不好,结果还是被刷了。人生真是寂寞如雪啊!

<think>嗯,用户想查找关于Linux Socket编程相关的面试题。让我先看看用户提供的引用内容。引用2提到Socket是网络通信的端点,引用3和5涉及select函数和关闭Socket的问题,引用4讨论了用户态缓冲区的重要性。用户可能正在准备面试,需要了解常见的Socket编程问题。 首先,我应该整理常见的Socket编程面试题,比如模型、函数使用、IO多路复用等。根据引用内容,select、缓冲区管理、关闭连接都是重点。需要覆盖基本概念、函数别、错误处理,以及相关工具如tcpdump的使用。 然后,要确保每个问题都有详细的解答,并正确引用对应的参考。例如,解释Socket基本概念时引用2,讨论select时引用3和5。用户态缓冲区的问题要结合引用4的内容。可能还需要补充三次握手和四次挥手的过程,虽然引用中没有直接提到,但这是Socket编程的基础。 还要注意格式,按照用户的要求使用中文,数学公式用$...$,独立公式用$$...$$。不过这里可能数学公式不多,主要是代码和术语。代码块需要用正确的语法,比如Python示例。 最后生成相关问题,比如三次握手、多进程模型等,帮助用户进一步复习。确保所有引用标记正确,例如[^1]、[^2]等。检查是否有遗漏的重要主题,比如非阻塞IO或常见错误处理,这些在面试中也很常见。</think>### Linux Socket编程常见面试题解析 #### 1. Socket编程的基本模型 Socket编程基于客户端-服务端模型,核心流程包括: - **服务端**:创建Socket -> 绑定地址 -> 监听连接 -> 接受连接 -> 数据读写 -> 关闭Socket[^5] - **客户端**:创建Socket -> 连接服务端 -> 数据读写 -> 关闭Socket ```python # 服务端示例(Python伪代码) sock = socket.socket() sock.bind(('0.0.0.0', 8080)) sock.listen() conn, addr = sock.accept() data = conn.recv(1024) conn.close() ``` #### 2. 关键函数别 | 函数 | 用途 | 系统调用次数 | |------------|-------------------------------|--------------| | `read()`/`write()` | 通用文件描述符操作 | 单次 | | `recv()`/`send()` | 专用于Socket的特化操作 | 单次 | | `recvmsg()`/`sendmsg()` | 支持辅助数据的高级操作 | 单次 | #### 3. IO多路复用技术对比 $$ \text{select}(n, readfds, writefds, exceptfds, timeout) $$ - **select**:跨平台支持,但最多支持$1024$个文件描述符(FD_SETSIZE限制)[^3] - **poll**:无数量限制,但需要遍历整个描述符列表 - **epoll**:Linux特有,使用红黑树实现$O(1)$时间复杂度 #### 4. 网络调试工具使用 - **tcpdump**:命令行抓包工具,支持表达式过滤 ```bash tcpdump -i eth0 port 80 -w capture.pcap ``` - **wireshark**:图形化协议分析工具(支持可视化过滤) #### 5. 用户态缓冲区必要性 - **读缓冲**:应对TCP粘包/拆包问题,需缓存不完整数据包[^4] - **写缓冲**:处理内核缓冲区满时的暂存需求 - **优化策略**:环形缓冲区、零拷贝技术(如`splice()`) #### 6. 连接关闭注意事项 - **半关闭**:`shutdown()`可单独关闭读/写通道 - **TIME_WAIT**:主动关闭方需等待$2MSL$(Maximum Segment Lifetime) - **资源泄漏**:未正确关闭Socket会导致文件描述符耗尽
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值