尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

奇安信Linux客户端开发笔试复盘:C/C++与系统编程核心考点

奇安信Linux客户端开发笔试复盘:C/C++与系统编程核心考点 刚考完奇安信2020客户端开发工程师-Linux开发的线上笔试趁热打铁把题目和思路捋一遍。先说结论这份笔试题不偏不怪考的全是Linux客户端开发天天要碰的底层基本功C/C基础、系统编程、网络编程占了大头穿插Linux常用命令和调试手段整体风格偏“底子检查”。如果你正在准备客户端或者Linux方向的校招笔试这篇文章值得收藏起来不光是真题回忆后面还把我这两周备考踩坑换来的整理思路一并放上了。先说下我的情况供你对照参考非科班自学C/C一年多Linux环境用了大半年但基本都是“会用命令、没看源码”的水平。5月31日下午场的笔试90分钟题型是单选多选编程题简答题。考完最大的感受是题目本身不难但考察面很宽任何一个环节薄弱都会在考场上卡壳。1. 奇安信这个岗位到底考什么笔试之前的岗位拆解我在投简历之前先干了件事——把“客户端开发工程师-Linux开发”这个岗位描述拆了一遍。别小看这一步它直接决定我这段时间复习的方向。如果连岗位要干什么都不清楚指望考前突击刷题大概率是白费。1.1 从JD反推技能树“客户端开发”这四个字看着简单但在Linux平台上它跟Windows客户端的套路完全不一样。Windows那边有Visual Studio全家桶、MFC、Qt Creator拖控件你有一半精力在处理UI和系统APILinux客户端这边更贴近“服务端视角”你写的程序通常跑在桌面环境或者嵌入式设备上要自己处理内存、进程、网络、日志甚至要用命令行工具去排查问题。从奇安信这家公司的安全背景倒推Linux客户端开发大概率涉及这几个方向终端安全产品比如EDR、杀软客户端、日志采集与上报、网络流量分析组件、系统监控模块。所以C/C基础、文件操作、进程管理、网络通信、多线程这些考点几乎必然会出现。我当时给自己定的复习重点就是C/C语言细节、Linux系统编程、TCP/UDP网络编程、常用命令与调试工具顺序按优先级排。1.2 笔试考察侧重点为什么这么考很多同学备考喜欢抱着leetcode猛刷觉得算法题做多了就能过关。但奇安信这份笔试题给我的感觉是算法题比重不大更看重“系统底层理解”。选择题里频繁出现的文件权限、进程状态、内存布局、网络状态机这些靠刷题是刷不出来的必须在真实Linux环境里跑过、看过、踩过坑才有感觉。我后来回看这份卷子它想筛选的人大概长这样能用C/C写Linux服务或客户端能读懂系统报错并排查问题对网络协议和并发有基础认知出了bug知道怎么用工具定位。换句话说它考的不是“你会不会写代码”而是“你能不能在这个岗位上直接干活”。1.3 备考资料的取舍我当时在网上搜了一堆“Linux面试题大全”“Linux命令大全”之类的资料发现内容良莠不齐有的纯粹是名词解释合集背完等于没背。后来我调整了策略以真题为圆心把每道题对应的知识点在Linux里实机操作一遍。比如看到“find命令按文件名查找”我不会只记参数而是真的去建几个目录、放几个文件把 -name、-type、-mtime、-exec 全跑一遍。看到“chmod 755 是什么意思”我会新建用户、切换权限、用 ls -l 观察权限位变化。这样记忆远比背表格牢固面试时被追问也不慌。这份卷子能答得七七八八跟这个“实机验证”习惯有直接关系。2. 笔试现场实录与题型复盘下面进入正题说说我印象里比较深的题目类型和具体考点。题目顺序记不太清了细节凭记忆还原但考察的知识点我反复核对过基本准确。2.1 选择题Linux基础占了大头选择题大概是30道左右单选多选混在一起多选少选都没分这个要格外注意。考到的点我列一下文件权限给出-rw-r--r--让你说出所有者、所属组、其他人的权限以及chmod 755和chmod ux有什么区别。这题属于送分题但如果你没在终端里敲过ls -l可能连第一列字段的顺序都说不清。进程与线程问进程和线程的区别、线程共享哪些资源不共享哪些资源。多选里有一道“线程不共享的是哪个”选项有堆、栈、全局变量、文件描述符答案是栈这个容易错。僵尸进程与孤儿进程问子进程先退出、父进程没调用wait()子进程会变成什么。这个考点几乎必考后面第3章我详细拆。内存布局问C程序的内存分区全局变量和局部静态变量分别存在哪堆和栈的生长方向。TCP连接三次握手、四次挥手问TIME_WAIT出现在哪一端、为什么要出现。这个也是高频考点客户端开发对接网络的时候尤其会碰到。Linux常用命令的冷门参数比如find按大小查找用什么参数、tar解压到指定目录用哪个选项、grep显示匹配行行号用哪个选项。这种题不难但平时不用就真的会卡住。2.2 编程题手撕代码没含糊编程题好像是两道还是三道我印象深的有这么几道字符串反转并去重给一个字符串要求反转顺序同时去掉重复字符。看起来简单但复杂度要求不能太高不能开额外数组硬存去重结果我当时用了一个bool标记数组记录ASCII码是否出现过。手写LRU缓存要求实现 get 和 put 两个方法容量固定超出容量要淘汰最久未使用的。这道题我在牛客题霸上刷过用的是“双向链表哈希表”的组合。多线程交替打印两个线程交替打印奇数和偶数打印到100。考察线程同步的经典题可以用互斥锁加条件变量做也可以用一个原子变量自旋做。编程题的环境是普通在线编辑器不是IDE没有补全提示所有代码纯手打。平时如果习惯了IDE的自动补全这里会很不适应所以考前务必在纯文本环境下手写过几道题。2.3 简答题考的是排查思路简答题不是背概念而是给你一个实际场景让你说出排查步骤。我记得有两道一个Linux进程CPU占用率持续100%怎么排查这题考的是工具链先用top看进程PID再top -H -p PID看线程再用perf或gdb attach看调用栈定位到具体函数。一个服务端口起不来报“Address already in use”怎么处理被问到这个的时候我背后一凉因为之前部署服务真遇到过。排查思路是netstat -tunlp | grep 端口号找到占用进程确认是否真的需要杀掉或者临时换端口。如果要杀掉用kill杀不掉再kill -9但也提到kill -9有风险要确认进程没有重要数据要落盘。这两道题如果只是在书上看过命令没有真刀真枪处理过答出来会很虚。面试官很容易接着追问“你当时是怎么发现是哪个进程占用的”完全拿实操经验说话。3. Linux方向高频考点逐题拆解笔试考完我复盘了一下发现考到的Linux知识点其实可以串成一条线从命令、文件、进程、内存、网络一层一层往上。这一章把我在考场上遇到的高频考点展开讲讲顺便补充一些实机验证的方法。3.1 Linux常用命令的底层逻辑别再背命令了“Linux常用命令大全”这种资料我见过太多了但把它们全部背下来没有意义。面试官想考的其实是你在什么场景下用什么命令、用了之后怎么看输出结果。比如ps aux和ps -ef输出字段略有不同但很多人只背参数不背输出含义考到ps aux里第几列是CPU使用率、第几列是内存使用率就懵了。一个比较实用的学命令方法按“场景”而不是按“字母表”学。比如“我想看这台机器上哪个进程最吃CPU”对应top加排序“我想找一遍日志里包含error的行并统计条数”对应grep error file | wc -l“我想把本机文件传到远端服务器”对应scp local_file userhost:/path。命令本身只是个工具真正值钱的是你面对问题时的思路。3.2 文件权限与目录操作笔试必考的细节文件权限几乎是Linux笔试前几道选择题里必出现的原因很简单客户端程序跑在用户态经常要读写配置文件、日志文件、临时文件权限不对程序就会崩。考场上考的是chmod 755的含义我把这个拆一下755是三个八进制数字分别对应所有者、所属组、其他用户的权限。每个数字由读(4)、写(2)、执行(1)三种权限相加得到。7421表示读、写、执行都有541表示只有读和执行没有写权限。所以chmod 755 file的效果是文件所有者能读写执行其他人只能读和执行。对应ls -l显示就是-rwxr-xr-x。另一个高频考点是“删除目录”。rm file删文件没问题删目录必须加-r也就是rm -r dir否则会报 “cannot remove ‘dir’: Is a directory”。如果要强制删除不询问加-f所以rm -rf dir是一个高危命令手滑一次可能把整个家目录都清空。笔试不一定会直接考但面试官聊项目的时候会问“你有没有误删过文件”如果你说用过这个命令还能活着反而能证明你平时工作确实在操作Linux。新建用户这块也常考比如useradd test和adduser test有什么区别。在Debian系系统上adduser是交互式创建用户的脚本会自动建家目录、设置密码useradd是底层命令默认不建家目录需要手动加-m参数。很多从CentOS切过来的同学在这里容易搞混。这类题目没有太多技巧就是在实机上建一遍用户看这两个命令的差别比背十遍笔记都快。3.3 进程与内存僵尸进程、孤儿进程、共享内存与消息队列笔试选择题里关于进程的坑我总结下来就三个高频点僵尸进程、孤儿进程、进程间通信方式对比。僵尸进程是“子进程先退出父进程还没来得及调用 wait() / waitpid() 回收它的退出状态子进程就变成了Zombie状态”。它在进程表里还占着一个位置但已经不执行任何代码了。如果父进程一直不回收僵尸进程会越积越多把系统进程表塞满导致新的进程无法创建。笔试可能只问你“僵尸进程是怎么产生的”“怎么避免”但面试极有可能追问“如果你的程序成了僵尸进程的父进程怎么处理”。处理思路是要么在代码里正确调用 waitpid()要么直接把父进程干掉让僵尸进程变成孤儿进程被 init/systemd 收养并回收。孤儿进程则是父进程先退出子进程被系统“过继”给1号进程继续运行不会变成无人管的状态。这两个概念放一起记就行“僵尸是死了但没人收尸孤儿是没人要但系统接管了”。进程间通信那一块考得比较多的是共享内存和消息队列的区别。共享内存是效率最高的IPC方式因为它直接把物理内存映射到进程地址空间省去了内核缓冲区的拷贝但缺点是要自己处理同步互斥。消息队列是内核维护的链表按消息类型读写避免了共享内存的同步问题但每次收发都有内核态拷贝性能比共享内存低。面试里如果再问“你项目里怎么选”只要答“数据量大、对性能要求高用共享内存信号量数据量小、需要按类型分发用消息队列”基本就稳了。3.4 网络编程TCP状态机是绕不开的坎客户端开发必然要碰网络笔试里TCP的题几乎不会缺席。印象最深的是TIME_WAIT那题很多同学只知道四次挥手有TIME_WAIT却不知道为什么要等2MSLMaximum Segment Lifetime报文最大生存时间。原因有两层第一保证最后一个ACK如果丢了还能有足够时间重传第二防止旧连接中的延迟报文出现在新连接里。这也是为什么大量短连接服务端会出现很多TIME_WAIT状态连接的原因客户端开发如果写长连接常驻反而不会太在意这个。还有一个高频选择是TCP三次握手第三次握手失败会怎样。很多人记不清。第三次握手失败时服务端会发送RST报文并释放这个半连接客户端收到RST后报错。这块如果光背状态名会非常痛苦建议自己在Linux下写一个简单的socket程序用strace或tcpdump抓一遍完整握手和挥手过程印象就深了。4. 编程题手撕实操从零实现到能跑笔试的编程题其实很能拉开差距有的人在IDE里能写出来换到无补全的网页编辑器就歇菜有的人虽然代码不整但思路完整。这一章我挑三道高概率遇到的题给出可运行的实现和我在考场上踩的坑。4.1 字符串反转并去重注意复杂度要求题目给定一个字符串反转它同时去除重复字符要求不能用额外的哈希集合且空间复杂度尽量低。我当时想到的方案是“标记数组法”因为字符集有限假设是ASCII 256个字符可以开一个bool seen[256]遍历字符串的每个字符遇到没见过的就标记同时放到新数组里最后逆序输出。这样时间O(n)、空间O(256)在笔试场景下够用了。#include stdio.h #include string.h void reverse_and_unique(char *s) { int len strlen(s); bool seen[256] {false}; char buf[256] {0}; int idx 0; for (int i 0; i len; i) { unsigned char c s[i]; if (!seen[c]) { seen[c] true; buf[idx] c; } } for (int i idx - 1; i 0; i--) { putchar(buf[i]); } putchar(\n); } int main() { char s[] hello linux; reverse_and_unique(s); return 0; }踩坑点字符数组下标要用unsigned char转型否则遇到中文或大于127的字符时char类型的负值会导致数组越界。这个细节我实际调试时遇到过笔试时也差点漏掉。4.2 手写LRU缓存双向链表哈希表LRU在面试里出现的频率不亚于反转链表笔试考“纯手写”时特别容易在指针操作上卡壳。核心思路是用一个哈希表存key对应的链表节点指针用一个双向链表维护访问顺序get/put命中时把节点移到链表头部超出容量时淘汰链表尾部节点。#include unordered_map using namespace std; class LRUCache { private: struct Node { int key, val; Node *prev, *next; Node(int k, int v) : key(k), val(v), prev(nullptr), next(nullptr) {} }; int cap; Node *head, *tail; unordered_mapint, Node* mp; void removeNode(Node* node) { node-prev-next node-next; node-next-prev node-prev; } void addToHead(Node* node) { node-next head-next; node-prev head; head-next-prev node; head-next node; } public: LRUCache(int capacity) : cap(capacity) { head new Node(-1, -1); tail new Node(-1, -1); head-next tail; tail-prev head; } int get(int key) { if (!mp.count(key)) return -1; Node* node mp[key]; removeNode(node); addToHead(node); return node-val; } void put(int key, int value) { if (mp.count(key)) { Node* node mp[key]; node-val value; removeNode(node); addToHead(node); } else { Node* node new Node(key, value); mp[key] node; addToHead(node); if ((int)mp.size() cap) { Node* last tail-prev; removeNode(last); mp.erase(last-key); delete last; } } } };笔试时我踩了一个坑忘了考虑更新已有key时先删后插的顺序导致链表里出现重复节点。建议动手写之前先画一遍链表结构图把“头尾哨兵节点”这个技巧记住代码会好写很多。4.3 TCP回显服务器socket编程完整流程如果笔试最后一题是“实现一个简单的TCP客户端/服务端”千万别慌。它考的是流程熟不熟不是代码多优雅。我默写的标准流程如下服务端socket() - bind() - listen() - accept() - recv()/send() - close()客户端socket() - connect() - send()/recv() - close()一个完整的最小回显服务端代码#include stdio.h #include string.h #include unistd.h #include arpa/inet.h #include sys/socket.h int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd 0) { perror(socket); return 1; } int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in addr; memset(addr, 0, sizeof(addr)); addr.sin_family AF_INET; addr.sin_addr.s_addr htonl(INADDR_ANY); addr.sin_port htons(8888); if (bind(listen_fd, (struct sockaddr *)addr, sizeof(addr)) 0) { perror(bind); return 1; } if (listen(listen_fd, 10) 0) { perror(listen); return 1; } while (1) { struct sockaddr_in client; socklen_t len sizeof(client); int conn_fd accept(listen_fd, (struct sockaddr *)client, len); char buf[1024]; int n recv(conn_fd, buf, sizeof(buf) - 1, 0); if (n 0) { buf[n] \0; send(conn_fd, buf, n, 0); } close(conn_fd); } close(listen_fd); return 0; }这里有个笔试也常考的坑服务端重启时报Address already in use。原因就是端口处于TIME_WAIT状态需要在bind()之前加setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, ...)。我上机实验时遇到这个问题后就再没忘记过这个API。5. 面试追问与细节补漏笔试只是第一关重点要预判面试官会从笔试卷里挖出哪些追问。这一章把我能想到的延伸问题整理出来相当于给笔试内容做个“二次复习”。5.1 面试官会追问什么笔试考文件权限面试就会问“你部署服务的时候日志文件权限是怎么设的会不会让别的用户也能看”。笔试考僵尸进程面试就会问“你负责的客户端程序有没有出现过僵尸进程怎么排查”。笔试考TCP三次握手面试就会问“如果客户端connect一个不存在的端口会报什么错怎么排查”。这类追问的底层逻辑都是把知识迁移到真实工作场景。比如“connect超时”这个点我认为值得重点准备。客户端在Linux环境下连接远端服务如果服务端没监听客户端connect通常会收到ECONNREFUSED如果网络不通或防火墙丢包客户端会一直等tcp_syn_retries重试默认可能长达2分钟。实际开发中前端界面会卡住所以要用非阻塞connect select/poll设置超时时间。这个知识点笔试选择题偶尔会考面试聊项目时更容易被勾起。5.2 客户端开发场景下的Linux知识笔试考的是通用基础但入职后你的工作场景会更具体。奇安信这类做安全产品的公司Linux客户端开发至少有这么几个现实场景日志采集客户端要定期写日志、切割日志你要懂logrotate的配置逻辑也要懂文件锁防止多进程同时写一个日志文件。崩溃分析客户端程序崩溃后会产生core文件你至少得会用gdb core看调用栈知道怎么查段错误、怎么定位空指针。开机自启Linux桌面客户端常常要配置Systemd服务或者桌面自启动项笔试不考但工作会碰到。与桌面环境交互如果做的是带界面的客户端会接触GTK或Qt还得处理输入法框架、字体库、桌面通知等琐碎问题。笔试不会直接考但面试问“你在Linux上开发GUI遇到最麻烦的事是什么”如果你能说出输入法在Qt程序里失效这类具体问题会非常加分。系统兼容性Linux发行版太多了不同的libc版本、不同的桌面环境、有没有systemd都会影响部署。面试官很乐意听到你说“我在Ubuntu 18.04/20.04和CentOS 7上都跑过”。这些场景如果考前没有概念哪怕笔试过了面试大概率也会露馅。所以备考时除了刷题我建议花点时间把Linux环境真正用起来哪怕只是安装虚拟机、配好开发环境、跑几个服务积累的“体感”是很难被速成的。5.3 复盘前先承认我踩了哪些坑我自己这次笔试有两大失分点写出来给大家避雷。第一个是多选题的“少选也错”规则。几道关于进程和网络的多选题我只选了绝对确定的选项漏选了也可以确定的选项直接0分。多选没有“部分给分”一说犹豫不定时宁可少选但不能盲目去蒙。第二个是代码题没在文本编辑器里演练过。平时用VS Code写代码括号补全和格式化都是自动的到了在线笔试的纯文本框里连{}都要手动对齐浪费了不少时间。考完痛定思痛我在终端里用vim练了一周写C程序再遇到在线编辑器就从容多了。另外还有一个教训是时间分配。我习惯先做编程题结果在LRU那道题上耗了太多时间后面简答题只能快速作答。回来复盘发现简答题其实只要写出关键命令和排查步骤就能拿分性价比远比手撕LRU高。以后遇到类似笔试我会先花两分钟扫一遍全卷把容易拿分的题目先做掉。6. 从笔试到OfferLinux客户端开发学习路线整理聊完真题最后这部分是给还处于准备期的人看的。如果你打算走Linux客户端开发这条路或者正在准备相关方向的笔试面试下面这套路线是我自己验证过、也认为比较高效的可以按顺序走。6.1 基础必学清单一步步来按优先级排序先掌握红色部分再扩展黄色部分优先级模块具体内容推荐验证方式高C/C语言基础指针、内存管理、static/const、构造析构、STL常用容器手写代码并编译运行高Linux系统编程文件I/O、进程fork、线程pthread、信号、IPC、socket写小型demo程序高网络基础TCP状态图、三次握手/四次挥手、select/poll/epoll区别tcpdump抓包验证中调试与排查gdb、strace、top、netstat、ss、perf用gdb调试一个段错误程序中构建与部署GCC编译参数、Makefile/CMake、动态库静态库、systemd从源码编译安装一个开源项目低桌面/GUIQt/GTK基础、信号槽、事件循环写一个带按钮的简单窗口低脚本与自动化Shell脚本、Python基础写一个批量重命名脚本这个顺序不是随便排的。前三个模块是笔试直接考、面试直接问的核心区中间两个模块是入职后天天用的生存技能后面两个模块是加分项如果时间紧张可以放一放。我当时就是按这个顺序推进的基本上笔试出题范围被覆盖了八成。6.2 实操环境搭建没有Linux环境一切复习都是纸上谈兵如果你手头没有Linux机器别犹豫先搭一个。我最推荐的是通过虚拟机安装Linux发行版选Ubuntu LTS或者Debian镜像官网下载就行装好之后建议把系统语言保持英文不然路径和报错信息容易误导你。装好系统后优先做这几件事配置SSH服务方便远程登录、安装vim和gcc/g/gdb、安装Docker、用Docker拉几个常用镜像比如Ubuntu、CentOS方便测试不同系统行为。如果你用的是Windows机器还可以在虚拟机里跑一个最小Linux系统用来练习命令行占资源少随开随用。这套环境搭好之后前面说的所有命令、编程题、网络抓包实验都可以在里面完成。如果不想装虚拟机路由器上刷个Linux系统、云服务器按量付费开一台最低配也都是可行方案。核心是“真的用起来”不是“装完拍个照就关掉”。我自己就是在虚拟机里把一个TCP服务端、一个客户端跑通后才真正理解socket那套抽象概念。6.3 推荐资源与书单少而精书不用贪多我真心看完了的只有几本每一本都值得反复翻《UNIX环境高级编程》APUE系统编程入门经典文件、进程、信号、线程、IPC都有建议配合实验逐个消化。《TCP/IP详解 卷1》网络协议的权威文献重点看TCP状态机和超时重传配合抓包工具学。《C Primer》C底子不牢的话先把这本书的核心章节过一遍重点看内存模型、拷贝控制、STL容器。《Linux命令行大全》不太适合系统学习更适合当字典查。我建议把它当目录看遇到不懂的命令去搜具体用法不要从头背到尾。除了书学习过程中要多用在线刷题平台练手。不要只刷算法题还要专门找“Linux命令题”和“系统编程题”来做。另外一个小技巧把你在书上看到的每个系统调用比如fork、pipe、mmap都写一个最小示例然后观察输出这一步能帮你避开大量笔试选择题的坑。6.4 面试前一周的冲刺策略考前一周不要再看新东西重点做三件事第一把高频选择题考点过一遍尤其是文件权限、进程状态、TCP状态直接用题测自己。第二手写代码保持手感每天至少写一道链表/字符串/多线程题编辑器就用纯文本框。第三准备一段“项目或实验经历”的口述稿内容不用高大上哪怕只是“我用虚拟机搭了一个Linux服务器部署了一个Python Flask应用配置了systemd服务让他开机自启”也比完全没有经历强。这个阶段还有一个实用技巧把常用的Linux命令整理成本地笔记按场景分类比如“查看端口占用”“查找大文件”“查看进程”“压缩解压”。不是让你背而是让你在面试前最后看一遍形成条件反射。我自己的笔记里记录了大量这类命令包括但不限于find按时间查找、scp递归传输、du -sh *查目录大小、df -h查磁盘空间这些基本就是Linux环境下开发同学的日常词汇表。最后再分享一个我自己的小习惯每次笔试或面试结束不管结果怎么样都花30分钟写一份复盘文档把不会的题、被追问卡住的点、时间分配的问题都记下来。一次两次不觉得有什么坚持几场之后你会发现自己对知识点的掌握会有一个非常明显的跳跃。这次奇安信笔试虽然有些题答得不够完美但复盘完这份笔记之后我对Linux客户端开发这个岗位需要什么能力心里反而比考前清晰得多。希望这篇记录也能帮你少走一点弯路。
返回列表