嵌⼊式常见⾯试题
LINUX 常见⾯试问题总结
声明:本⽂是将常见的⾯试问题进⾏汇总,但⼤多数问题也是开发中较为常见的技术盲区!在此进⾏了汇总,以便后续进⾏参考!所有的答案部分是⾃⼰编写,部分问题答案引⽤博客客友的优秀⽂章!问题不全,后续⼯作及学习中会不断更新!
如引⽤下⾯⽂章对问题进⾏解答:
1)、
2)、
3)、
4)、
5)、
6)、
7)、
感谢以上客友的经典⽂章!
1、什么是嵌⼊式?
正弦波信号发生器A: 嵌⼊式系统本⾝是⼀个相对模糊的定义。⽬前嵌⼊式系统已经渗透到我们⽣活中的每个⾓落,⼯业、服务业、消费电⼦……,⽽恰恰由于这种范围的扩⼤,使得“嵌⼊式系统”更加难于明确定义。以下是⼏种常见表达⽅式: 1、执⾏专⽤功能并被内部计算机控制的设备或者系统。嵌⼊式系统不能使⽤通⽤型计算机,⽽且运⾏的是固化的软件,⽤术语表⽰就是固件(firmware),终端⽤户很难或者不可能改变固件。 2、凡是专⽤的、⼩型或者微型的计算机系统都是嵌⼊式系统,⽐如MP3, ⼿机,⾼清电视
3、⽐较传神和从技术⼈员⾓度来看,嵌⼊式系统是以应⽤为中⼼,以计算机技术为基础,并且软硬件可裁剪,适⽤于应⽤系统对功能、可靠性、成本、体积、功耗有严格要求的专⽤计算机系统。
2、字符设备和块设备的区别?A:1、字符设备和块设备、⽹络设备是⼀个并列的概念2、字符设备按照字符流的⽅式被有序访问,块设备以块为单位;⼆者根本区别在于字符设备只能顺序被读写,块设备可以随机访问
3、Linux为块设备和字符设备提供了两套机制。字符设备实现的⽐较简单,内核例程和⽤户态API⼀⼀对应,⽤户层的read函数直接对应了内核中的read例程,这种映射关系由字符设备的file_operations维护。块设备接⼝相对于字符设备复杂,read、write API没有直接到块设备层, ⽽是通过IO请求的⽅式通过OS的IO请求队列实现。内核管理块设备要⽐管理字符设备细致得多,内核对块设备的管理却提供⼀个专门的提供服务的⼦系统。块设备对执⾏性能的要求很⾼;,LINUX内核开发者门⼀直致⼒于优化块设备的驱动。
进程与程序的联系
A:
1. 程序是⼀组指令的集合,它是静态的实体,没有执⾏的含义。进程程序的执⾏过程,是⼀个动态的实体,有⾃⼰的⽣命周期,包括产⽣、运⾏、消亡的过程。除此之外,进程还有并发性和交往性。简单地说,进程是程序的⼀部分,程序运⾏的时候会产⽣进程。
2.所涉及到的介质不同,程序保存在存储介质,⽐如FLASH,硬盘等中,进程运⾏在RAM中内容不完全相同,程序有数据段,代码段,调试信息等,进程执⾏时候,有代码段,数据段,以及堆栈如有侵权,请联系!
1
线程和进程的区别:
A:
1、线程是进程的⼀部分,所以线程有的时候被称为是轻权进程或者轻量级进程。
2、⼀个没有线程的进程是可以被看作单线程的,如果⼀个进程内拥有多个进程,进程的执⾏过程不是⼀条线(线程)的,⽽是多条线(线程)共同完成的。
3、系统在运⾏的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使⽤的资源是它所属的进程的资源),线程组只能共享资源。也就是 说,出了CPU之外(线程在运⾏的时候要占⽤CPU资源),计算机内部的软硬件资源的分配与线程⽆关,线程只能共享它所属进程的资源。
4、标准LINUX的进程具有独⽴的虚拟地址空间,⽽⼀个进程⾥⾯的多个线程共享同⼀个虚拟内存空间,进程是系统所有资源分配时候的⼀个基本单位
4、嵌⼊式的移植过程
下⾯我们就来看下⼀个内容叫做移植的基本步骤,也就是说我们要现有⼀个⼤体的思路,如果说我作为产品开发者,或者说是作为⼀个系统的整体架构来说,我们拿到⼀款板⼦过后我们是如何⼀步⼀步把我们的系统⽤起来呢?它整个系统流程⼜是什么样的,我们先要有个明确⽬标,第⼀个⽬标是我们要保证PC也就是我们的开发机器跟⽬标机也就是开发板或者说最终要做成产品的板⼦的硬件它们俩之间的连接⽅式。
因为我们在嵌⼊式开发中有⼀个很⿇烦的事情就是开发板的能⼒跟PC的能⼒⼀般是不平等的,⼤家都知道PC的功能很强⼤也很贵⽽板⼦很便宜可能⼀个⼩系统⼀个路由器也就⼏⼗块钱,但是我们总不能在路由器上接个键盘接个⿏标然后装⼀个VC,在这⾥是不现实的,所以说我们⼀般的开发环境跟ARM讲的内容都是⼀样的,都是在主机上开发最终把主机编译好的内容跟我们的⽬标机进⾏⼀个数据传输,所以这就涉及到⼀个⾮常重要的问题。
数据传输的⽅式。因为我们数据⽆外乎就是⾼低电平这⼏种,那么传播有哪些传播⽅式呢?
如图,所以我们如果作为⼀个产品的研发者来说,你第⼀个需要考虑的就是我们是怎么连的。
那么给⼤家来列出了⼀下,⽬前来说,我们的PC跟我们的开发板的连接也就⼤概如上图四种⽐较常⽤
僧侣鞋
的⽅式。第⼀种就是我们最经典90%的板⼦上,都⽀持的⽅式叫异部串⾏接⼝,也就是我们所说的串⼝。那么这个串⼝传输在我们之前学习ARM的时候也学习到过,其实别看它很简单,其实它的功能很强⼤。它既可以输⼊也可以输出,所以说我们基本上完成了⼀个输⼊输出这样数据出和进的功能。
所以说串⼝是我们⽐较常⽤的⼀个接⼝。但是它还是有它的特点就是它的速度⽐较低,因为⽐如说我们前⾯所配的速度是11520那这个其实是很低的,不是很⾼。因为他11520B⽐特也就是传多少位多少个⾼低电平的字节,所以说这个效率不是很⾼但是实⽤性⽐较强就⼏个接⼝就可以。
我们举个典型的例⼦就是家⾥的路由器,如果⼤家有兴趣,就把家⾥的⽼路由器拆下来看⼀下路由器⾥⾯⼀般都有3个架⼦或4个架⼦,3个⼩插针或4个⼩插针⽆外乎就⼏个电压,⼀个是D⼀个是电源很多情况下,路由器都会引出这个东西。如果你的动⼿能⼒⽐较强,你就去市场上买⼀个叫DB9的⼀个⼩头⼦,拿个烙铁把那⾥⾯的⼏根线给焊上去。然后就跟PC⼀接,就可能会看到路由器的⼀些打印信息。所以说串⼝在我们嵌⼊式开发中算是⼀个⾮常经典的跟PC之间通信的⼀个接⼝。
因为⼤家可以想⼀下,这个串⼝既可以输出我们可以把开发板上的信息往我们的平台上去看。 甚⾄来说,我们还可以通过串⼝把PC⾥⾯的东西传到开发板中,所以他说输⼊输出都可以,这样的话串⼝也算是⼀个⽐较万能的接⼝,它唯⼀的缺陷就是速度太低,如果我们传输⼀些⼤数据⽐如说以后我们会看到的安卓中的⼀些东西,安卓中涉及到的其实跟我们所学的也是⼀样,他⽐较⿇烦的就是⽂件系统
⽀柱,⽂件系统少的可能就要⼏百兆,或者说压缩过后就是⼏⼗兆。那你可以想象⼀下,我们如果⽤11520去传,有的时候就要传送⼀⼆⼗分钟,这样很影响开发效率的。
所以说⽤串⼝如果是⼩⽂件没有关系,但⼤⽂件⼀般情况下⽤串⼝传输的可能性不⼤。如果说你的板⼦功能⽐较强,传的东西⽐较多,这种情况下⽤串⼝我们还是不建议。
那么现在我们就需要换⼀下,串⼝我们可以把它当作后备资源。
然后我们就要使⽤如上图所说的USB。随着USB的发展,从USB1.0到2.0、3.0,它的速度越来越快。那么这个传输数据我们就不⽤担⼼,它速度快是没有问题的,但是现在唯⼀⽐较担⼼的⼀个问题就是板⼦刚刚上电,就让他⽤串⼝去⼯作,这个是不现实的。所以这种情况下,我们还涉及到在开发板要把串⼝的驱动做好。所以说这个时候我们还要考虑驱动的问题,到底⽀不⽀持如果不⽀持或者开发的周期⽐较长,那么串⼝就不把它作为⽬标机和主机连接的主要⽅式了。以上就是我们的串⼝。
串⼝退⽽求其次就还有⼀种叫做⽹络接⼝,这个⽹络接⼝也是我们嵌⼊式开发中使⽤很普遍的⼀个接⼝。
如上图因为这个TCP/IP这个协议已经很成熟了。
因为我们的PC本⾝就是TCP/IP中很重要的⼀个端⼝,⽐如你可以作为服务器,也可以作为客户端,
然后我们的开发板也只需要跑⼀个服务器或者客户端就可以跟PC以CS的模式进⾏数据的传输和下载。所以说这个⽅式也是⽐较通⽤的⽽且说⽹卡的数据和速率都⽐较快最少最少都是⼗兆⽽且现在百兆⽹卡都是⾮常多的。这样的话,传输速率肯定是⽐11520是快得多。
所以说在我们后期课程中,⼤家会看到我们通过⽹络接⼝去下载数据的情况是⽐较多的。
驱动也是⼀样的它也需要移植,但相对⽽⾔,它可能要⽐USB上要⽅便⼀点,因为USB它涉及到速率,就是说它有些时序需要调整,可能会有⼀些⿇烦,所以说对于TCP/IP中的⽹卡我们⼀般来说是优于USB去选择的。
最后⼀个叫做Debug Jtag调试接⼝,也就是说如果你是ARM CPU的话,那么ARM中还有⼀些相关Debug Jtag的ICE,也就是说它内部会集成⼀些这样的东西,然后可能你的⼚商会提供这样的调制接⼝。⽐如说ARM9就有,但是像A系列的开发板⼀般来说很少在市⾯上能够买得到它的调制接⼝。也就是Debug Jtag调试接⼝。也不是说没有,我们曾经联系ARM公司问过像Debug Jtag调试接⼝⼀台就要⼀万块钱,所以这样来说,如果你为了学习花⼀万块钱是得不偿失的。所以这种情况除⾮真的是你们公司去开发跟ARM公司出芯⽚⽐较多才可能去买⼀台Debug Jtag调试接⼝。
当然Debug Jtag调试接⼝很⽅便,⽐上⾯三种都更好调试,调试效率也要⾼的多但是就是价格太⾼了,所以说我们个⼈学习已经很多企业来说更多的还是⽤以上的三种。
以上就是我们移植的第⼀个步骤,就是关于我们主机和⽬标机的连接⽅式。
交叉编译器
有了上⾯那个连接⽅式下⼀个就是交叉编译器了。
交叉编译器,在我们后⾯就会讲到所谓的交叉编译器它其实就是我们很多情况下在开发项⽬中必备的⼀个⼯具。因为我们⼀般的开发在PC,⽽PC很显然⼤家知道它的架构是在X86,但现在很显然,我们X86的程序跟ARM程序肯定是不兼容的,这样的话我们就需要⼀个交叉编译器来进⾏相应的编译开发,也就是说我们开发的程序不能开发成X86,也就是说X86⾥⾯的⼆进制程序下载到ARM开发板上ARM是不认识的,这样的话,程序是不能相通的,所以说以上就是我们需要安装交叉编译器的⼀个道理。
如图,安装交叉编译器也有两种⽅法,⼀般情况下是第⼀种就是芯⽚⼚商已经给好的因为你买的芯⽚⼀般都有。第⼆种就是我们⾃⼰⼿动的去编译,⾃⼰动⼿编译交叉⼯具链,⼿动编译如果⼤家想做可以做但是它⾮常耗时间甚⾄来说遇到的问题可能会特别特别多,他有很多不兼容的问题这个他是需要⼀定的功底去调试⾮常⿇烦,所以不推荐新⼿去做,甚⾄很多公司都不会⾃⼰贸然⽤编译器去做产品开发。
捕鼠笼
但是如果有兴趣的同学可以下去搜索⼀本书“The GNU Toolchain for ARM Target HOWTO”这其实也是⼀本官⽅⼿册,它会告诉你如何去做⼀个相关的⼯具链。当然这个⼯具念在制作的时候,他的⽅法⽐较单⼀,思路也⽐较单⼀,那唯⼀不好,就是他的版本之前依赖关系⽐较⼤,你可能会需要⼀些⼿动的去修改这些要做的⽐较多,所以对于编译原理你要知道的很清楚去到问题所在点去编,所以相对⽽⾔⽐较耗时甚⾄编不通过很⿇烦,所以这个部分⼀般情况下不建议使⽤。
更多时候我们⽤的是芯⽚⼚商提供好的,那么芯⽚⼚商提供的芯⽚,⼀般情况下有这么⼏种前缀名。
如图,就说在安装过后,都会有⼀些前缀名,我先简单的说⼀下它的意义。最常⽤的是这⼀种叫做 “arm-none-linux-gnueabi-”他的意思是第⼀例就是说⽬标题结构也就是说,这个⼯具⽬的是做什么的,⽐如说最典型的gcc什么都没写他默认就是编译X86,如果我这⾥有ARM说明他这个编译器编译的是ARM,记住这个编译器他其实是个集合,我只列出了前缀也就说他这个地⽅实际上隐含了⼀个概念叫gcc,也就是说相当于⽤了图上加⿊的那个⼯具,只是说,我们现在更多的时候是⽤的⼀个前缀,我们后⾯还会讲到很多⼯具集,所以我们记这个前缀⽽不记后⾯的这个命令,前缀的第⼀个单词就是你最终⽣成的编译器和⽣成的体系结构,第⼆个就是⼚商名⼀般如果你是开源的话就是none,⽐如说你是三星的话那就是ARM_sanxin,⼀般都是none。然后第三个linux也就是我们这个程序默认编
译出来的功能是针对linux操作系统去⽤的,也就是这个好处在于这个编译器它的内部有⼀些标准C库⽽这个C库是跟linux的接⼝相关的,也就是这个软件编译出来的可执⾏程序不能在windows下运⾏的。 这样的话这个⼯具链就专门针对于Linux操作系统运⾏。⽽且是ARM下的Linux操作系统⽽不是Windows,然后后⾯的那个词gun⼤家都知道是开源的,⽽eabi指的是我们嵌⼊式的标准接⼝,它主要针对的是嵌⼊式精简的⼀些相关库所以说是eabi,还有⼀个是oabi是⽼的,我们现在基本上都是⽤的eabi的系统,主要由ARM的优化选项来决定的,这个我们到时候看到ARM优化的那⼀章内容可以去学习⼀下。
⼀般情况下“arm-none-linux-gnueabi-”这个名字太长了我们也不想去记所以就⼲脆把它简称为“arm-linux-”所以有些时候你经常会看到有些编译器中都做了⼀个⼩技巧,把“arm-linux-”和“arm-none-linux-gnueabi-”做了⼀个快捷⽅式,就是软链接,把两个名字关联起来,也就是输⼊其中⼀个名字就相当于在输⼊另⼀个名字。所以这⼀组就是⽐较常⽤的形式。
后⾯还可能会接触⼀些,⽐如“arm-none-eabi-”就是没有Linux的,没有Linux这款编辑器⼀般代表不能在有操作系统的ARM上运⾏,因为它不⽀持操作系统,所以这就是⼀款⽐较新的,⽽下⾯这个“rm-elf-”是⾮常⽼的,也是针对于⽆操作系统或者可能有操作系统也⽀持,只是这个是⾮常⽼的,很少能见到。主要见到的是前两种。
以上就是我们安装的⼯具链,其实⼯具链⼀般来说⼚商要就可以或者说你实在要不到也没关系,⽐如说下载安卓后其实它⾃动就把编译器给编译出来了,你可以直接把它拷出来⽤也可以。或者说你可以去⼀个公司,这个公司现在已经被收购了,但是⼤家可以注意去查叫
做“codesourcery”虽然它已经被收购,但是它其实也是提供交叉编译器的⼯具,⼤家如果有兴趣也可以去⽹上搜⼀下,然后去注册⼀下那个收购公司的账号下下来也可以。但是⼀般来说,⽤我们光盘已经配置好的编译器也问题不⼤。
其上就是我们说的第⼆个部分安装交叉编译器。
⽬标机传输通道
第三个部分:搭建主机,⽬标机传输通道。我们就要想办法确定连接⽅法后就要准备搭建传输通道的⼀些所具备的服务和客户。⽐如说我们⽤⽹络,最显然就涉及到服务配置。
如图,有服务器有客户端,这样来说⽹络就可以成功传输数据。所以说⼀般情况下在嵌⼊式中我们⽤的⽐较多的服务就两个,在Linux下⼀个叫TFTP⼀个叫NFS,TFTP顾名思义就是FTP的⼀个简版,它是基于UDP传输的,相当于它的协议⽐较简单。⽽NFS它的全名叫做⽹络⽂件系统,这个⽹络⽂件系
统主要是Linux和Linux之间做挂载⽤的,那么这个⽤处应该是⾮常⼤,⽐如说我们在后⾯学⽂件系统的调试的时候,我们很多时候都会⽤NFS作为我们调试的⼀个基本应⽤⼯具,所以这也是我们在搭建开发环境中的第三步。
烧写测试
最后,都准备好后,剩下就是⼀件事情也是最难的⼀件事情,就是把我们之前那三个⼦系统的功能全部做好,假如我们已经有这样的功能我们就把它们相应的编译出来,编译后剩下的最后⼀步就是烧写测试,最后把它整个集成放到⼯⼚然后就开始集成化⽣产。
以上,就是基本的移植步骤,⽽具体编译三⼤系统就是我们后⾯的课程需要掌握的内容,最后我们在每⼀个内容中都涉及到怎么去烧写它。
5、守护进程的编写步骤
什么是守护进程?
答:守护进程是后台运⾏的、系统启动是就存在的、不予任何终端关联的,⽤于处理⼀些系统级别任务的特殊进程。
优糖米
实现思路:
实现⼀个守护进程,其实就是将普通进程按照上述特性改造为守护进程的过程。
需要注意的⼀点是,不同版本的 Unix 系统其实现机制不同,BSD 和 Linux 下的实现细节就不同。
根据上述的特性,我们便可以创建⼀个简单的守护进程,这⾥以 Linux 系统下从终端 Shell 来启动为例。
在此有必要说⼀下两个概念:会话和进程组。
进程都有⽗进程,⽗进程也有⽗进程,这就形成了⼀个以init进程为根的家族树。除此以外,进程还有其他层次关系:进程、进程组和会话。进程组和会话在进程之间形成了两级的层次:进程组是⼀组相关进程的集合,会话是⼀组相关进程组的集合。
这样说来,⼀个进程会有如下ID:
·PID:进程的唯⼀标识。对于多线程的进程⽽⾔,所有线程调⽤getpid函数会返回相同的值。
·PGID:进程组ID。每个进程都会有进程组ID,表⽰该进程所属的进程组。默认情况下新创建的进程会继承⽗进程的进程组ID。
nfj金属防静电不发火·SID:会话ID。每个进程也都有会话ID。默认情况下,新创建的进程会继承⽗进程的会话ID。
前⾯提到过,新进程默认继承⽗进程的进程组ID和会话ID,如果都是默认情况的话,那么追根溯源可知,所有的进程应该有共同的进程组ID 和会话ID。但是调⽤ps axjf可以看到,实际情况并⾮如此,系统中存在很多不同的会话,每个会话下也有不同的进程组。
为何会如此呢?
就像家族企业⼀样,如果从创业之初,所有家族成员都墨守成规,循规蹈矩,默认情况下,就只会有⼀个公司、⼀个部门。但是也有些“叛逆”的⼦弟,愿意为家族公司开疆拓⼟,愿意成⽴新的部门。这些新的部门就是新创建的进程组。如果有⼦弟“离经叛道”,甚⾄不愿意呆在家族公司⾥,他别开天地,另创了⼀个公司,那这个新公司就是新创建的会话组。由此可见,系统必须要有改变和设置进程组ID和会话ID 的函数接⼝,否则,系统中只会存在⼀个会话、⼀个进程组。
电子蜂毒采集器进程组和会话是为了⽀持shell作业控制⽽引⼊的概念。
当有新的⽤户登录Linux时,登录进程会为这个⽤户创建⼀个会话。⽤户的登录shell就是会话的⾸进程。会话的⾸进程ID会作为整个会话的ID。会话是⼀个或多个进程组的集合,囊括了登录⽤户的所有活动。在登录shell时,⽤户可能会使⽤管道,让多个进程互相配合完成⼀项⼯作,这⼀组进程属于同⼀个进程组。
当⽤户通过SSH客户端⼯具(putty、xshell等)连⼊Linux时,与上述登录的情景是类似的。
通常,会话开始于⽤户登录,终⽌于⽤户退出,期间的所有进程都属于这个会话。⼀个会话⼀般包含⼀个会话⾸进程、⼀个前台进程组和⼀个后台进程组,控制终端可有可⽆;此外,前台进程组只有⼀个,后台进程组可以有多个,这些进程组共享⼀个控制终端。
前台进程组:
该进程组中的进程可以向终端设备进⾏读、写操作(属于该组的进程可以从终端获得输⼊)。该进程组的 ID 等于控制终端进程组 ID,通常据此来判断前台进程组。
后台进程组:
会话中除了会话⾸进程和前台进程组以外的所有进程,都属于后台进程组。该进程组中的进程只能向终端设备进⾏写操作。
下图为会话、进程组、进程和控制终端之间的关系(登录 shell 进程本⾝属于⼀个单独的进程组)。
想了解更多关于会话 Sessions 内容,可以认真读⼀下 这本书。
如果调⽤进程⾮组长进程,那么就能创建⼀个新会话:
该进程变成新会话的⾸进程
该进程成为⼀个新进程组的组长进程
该进程没有控制终端,如果之前有,则会被中断(会话过程对控制终端的独占性)
也就是说:组长进程不能成为新会话⾸进程,新会话⾸进程必定成为组长进程。
1、fork()创建⼦进程,⽗进程exit()退出;
这是创建守护进程的第⼀步。由于守护进程是脱离控制终端的,完成这⼀步后就会在Shell终端⾥造成程序已经运⾏完毕的假象。之后的所有⼯作都在⼦进程中完成,⽽⽤户在Shell终端⾥则可以执⾏其他命令,从⽽在形式上做到了与控制终端的脱离,在后台⼯作。
由于⽗进程先于⼦进程退出,⼦进程就变为孤⼉进程,并由 init 进程作为其⽗进程收养。
2、在⼦进程调⽤setsid()创建新会话;
在调⽤了 fork() 函数后,⼦进程全盘拷贝了⽗进程的会话期、进程组、控制终端等,虽然⽗进程退出了,但会话期、进程组、控制终端等并没有改变。这还不是真正意义上的独⽴开来,⽽ setsid() 函数能够使进程完全独⽴出来。
setsid()创建⼀个新会话,调⽤进程担任新会话的⾸进程,其作⽤有:
使当前进程脱离原会话的控制
使当前进程脱离原进程组的控制
使当前进程脱离原控制终端的控制
这样,当前进程才能实现真正意义上完全独⽴出来,摆脱其他进程的控制。
3、再次 fork() ⼀个⼦进程,⽗进程exit退出;
现在,进程已经成为⽆终端的会话组长,但它可以重新申请打开⼀个控制终端,可以通过 fork() ⼀个⼦进程,该⼦进程不是会话⾸进程,该进程将不能重新打开控制终端。退出⽗进程。
也就是说通过再次创建⼦进程结束当前进程,使进程不再是会话⾸进程来禁⽌进程重新打开控制终端。
4、在⼦进程中调⽤chdir()让根⽬录“/”成为⼦进程的⼯作⽬录;
这⼀步也是必要的步骤。使⽤fork创建的⼦进程继承了⽗进程的当前⼯作⽬录。由于在进程运⾏中,
当前⽬录所在的⽂件系统
(如“/mnt/usb”)是不能卸载的,这对以后的使⽤会造成诸多的⿇烦(⽐如系统由于某种原因要进⼊单⽤户模式)。因此,通常的做法是让"/"作为守护进程的当前⼯作⽬录,这样就可以避免上述的问题,当然,如有特殊需要,也可以把当前⼯作⽬录换成其他的路径,
如/tmp。改变⼯作⽬录的常见函数是chdir。(避免原⽗进程当前⽬录带来的⼀些⿇烦)