Linux程序设计上机指导书3:Linux进程控制
Linux进程控制

exec与fork配合使用的效率问题
• 在Unix时代exec经常与fork配合使用,但这样做了大 量的无用功,效率低下(为什么?)。
// 父进程做其他事情 return 0; }
exec函数族
exec函数族使用区别
• exec函数族使用区别
– 查找方式 • 表中的前四个函数的查找方式都是完整的文件目录路径,而最 后两个函数(以p结尾的函数)可以只给出文件名,系统就会自动 从环境变量“$PATH”所指出的路径中进行查找。
– 参数传递方式 • 两种方式:逐个列举、将所有参数整体构造指针数组传递 • 以函数名的第五位字母来区分的,字母为“l”(list)的表示逐个 列举的方式,其语法为char *arg;字母为“v”(vertor)的表示 将所有参数整体构造指针数组传递,其语法为*const argv[]
Fork函数的应用逻辑—孙悟空逻辑
int main() {
… …. // 遇到两个需要并行执行的任务:任务1和任务2 pid = fork(); // 分身术 if (pid == 0) { …… // 子进程处理任务1 } else { …… // 父进程处理任务2 } return 0; }
Fork函数应用例1
Linux进程状态
• Linux中任务和进程是相同的术语,每个进程由 task_struct 结构来描述,即PCB (进程控制块)
• Linux 将进程状态主要分为五种: – TASK_RUNNING – TASK_INTERRUPTIBLE – TASK_UNINTERRUPTIBLE – TASK_STOPPED /TASK_TRACED – TASK_ZOMBILE 。
Linux程序设计实验指导书

Linux程序设计实验指导书10/ 1前言根据教学大纲与实验大纲的安排,本课程计划进行8课时上机试验。
由于所有实验均涉及操作系统、程序设计等课程的多个知识点,希望各位同学认真准备。
?上机实验前应充分做好以下准备工作:1.复习和掌握与本次实验有关的教学内容。
2.根据本次实验的内容,在纸上编写好准备上机调试的程序,并初步检查无误。
3.准备好对程序进行测试的数据。
4.对每种测试数据,给出预期的程序运行结果。
5.预习实验步骤,对实验步骤中提出的一些问题进行思考。
?上机实验后,应及时写出实验报告,实验报告应包括以下内容:1.实验目的和内容。
2.程序说明,包括程序结构、各模块的算法。
3.调试正确的源程序。
4.程序运行记录(包括对不同测试数据的运行结果)。
5.针对实验中出现的问题,写出解决办法及对运行结果的分析。
本指导书适用于物联网工程专业学生学习“Linux程序设计”课程时实验使用。
10/ I.实验一:熟悉Linux实验学时:2实验类型:(验证)实验要求:(必修)一、实验目的Linux操作系统是多任务操作系统,对进程与线程提供完整支持。
本次实验意图通过实践掌握进程的基本概念,理解进程控制、进程同步、经典进程的同步问题、管程机制、进程通信、线程;初步熟悉多进程/多线程编程的特点。
Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。
它能运行主要的UNIX工具软件、应用程序和网络协议。
它支持32位和64位硬件。
Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。
本次实验意图通过实践了解Linux环境中常见的终端命令;熟悉文本编辑器leafpad 的使用。
二、实验内容本次实验的实验内容包括:1)熟悉man、cd、mkdir、rmdir、chmod、chown、ps、cat、ls命令;2) 练习使用文本编辑器leafpad。
使用Linux终端管理进程和任务

使用Linux终端管理进程和任务Linux终端是一种强大的工具,可以用来管理和监控系统中运行的进程和任务。
本文将介绍如何使用Linux终端来管理进程和任务,以提升系统性能和效率。
一、查看进程在Linux终端中,可以使用ps命令来查看系统中正在运行的进程。
ps命令有多种不同的选项,可以根据需求选择不同的选项来获取进程的详细信息。
1. ps aux该命令可以显示系统中所有用户的进程信息,并以列表形式进行展示。
每个进程的信息包括进程ID(PID)、CPU使用率、内存占用、进程状态等。
例如:```$ ps aux```2. ps -ef该命令显示所有进程的完整信息,包括进程之间的父子关系。
例如:```$ ps -ef```3. ps -u该命令可以按照用户名来筛选进程,并只显示该用户的进程信息。
例如:```$ ps -u username```二、管理进程在Linux终端中,可以使用kill命令来终止运行中的进程。
kill命令可以接受进程ID作为参数,或者使用进程名称来终止进程。
1. 终止进程使用kill命令终止进程时,需要指定进程的PID。
例如,要终止进程ID为123的进程,可以使用以下命令:```$ kill 123```2. 强制终止进程有时候进程可能无法正常终止,这时可以使用kill命令的-9选项强制终止进程。
例如:```$ kill -9 123```三、任务管理除了进程管理,Linux终端还可以方便地管理系统中的任务。
任务是指在终端中运行的命令或程序。
1. 后台运行任务在Linux终端中,可以使用&符号将任务放入后台运行。
例如,要在后台运行一个脚本文件,可以使用以下命令:```$ ./script.sh &```2. 列出后台任务使用jobs命令可以列出当前终端中所有正在运行的后台任务。
例如:```$ jobs```3. 暂停和恢复任务使用Ctrl+Z组合键可以将当前正在运行的任务暂停,使用fg命令可以将任务从后台恢复到前台运行。
Linux C第3次上机作业(进程控制)

#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid,int *status,int options);
pid参数
1. pid>0时,只等待进程ID等于pid的子进程,不管 其它已经有多少子进程运行结束退出了,只要指定 的子进程还没有结束,waitpid就会一直等下去。 2. pid=-1时,等待任何一个子进程退出,没有任何 限制,此时waitpid和wait的作用一模一样。 3. pid=0时,等待同一个进程组中的任何子进程, 如果子进程已经加入了别的进程组,waitpid不会对 它做任何理睬。 4. pid<-1时,等待一个指定进程组中的任何子进程 ,这个进程组的ID等于pid的绝对值。
options参数
options提供了一些额外的选项来控制waitpid,目 前在Linux中只支持WNOHANG和WUNTRACED两个选项, 这是两个常数,可以用“|”运算符把它们连接起来 使用,如:
ret=waitpid(-1,NULL,WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0
当pid所指示的子进程不存在,或此进程存在,但 不是调用进程的子进程,waitpid就会出错返回。子进程显示自己的进程 号(PID)后暂停一段时间(10秒),父进程等待子进程正常 结束,打印显示等待的进程号(PID)。(分别用wait函数和 waitpid函数实现)。 2.设计一个程序,创建子进程,子进程在运行时执行vim程 序,并查看程序的进程号与vim的进程号。(用system函 数族实现)(vim在user/bin目录下)
1、WIFEXITED(status) 这个宏用来指出子进程是否为正常退出的,如果是, 它会返回一个非零值。 2、WEXITSTATUS(status) 当WIFEXITED返回非零值时,我们可以用这个宏 来提取子进程的返回值。
LINUX 的进程控制

printf(“I am parent, my rid is %d, my PID is %d\n”, rid, getpid()); else // 子进程分支
printf(“I am child, my rid is %d, my PID is %d\n”, rid, getpid()); }
注:程序中的getpid()是一个系统调用,它返回本进程 的进程标识号PID。 fork_test程序运行时,父子进程将会输出不同的信息, 如父进程的输出可能是“I am parent, my rid is 8229, my PID is 8228”;子进程的输出可能是“I am child, my rid is 0, my PID is 8229”。由于两进程是并发的,它们输出信息的先后 次序不确定,有可能父先子后,也可能相反。
与一般的函数不同,exec()是“一次调用,零次返回”, 因为调用成功后,进程的映像已经被替换,无处可以返回了。 图4-9描述了用exec()系统调用更换进程映像的流程。子进程 开始运行后,立即调用exec(),变身成功后即开始执行新的 程序了。
图4 用exec更换子进程的映像
例2 一个简单的fork-exec_test程序:
2) 终止进程 进程无论以哪种方式结束,都会调用一个exit()系统调 用,通过这个系统调用终止自己的运行,并及时通知父进程 回收本进程。exit()系统调用完成以下操作:释放进程除 PCB外的几乎所有资源;向PCB写入进程退出状态和一些统 计信息;置进程状态为“僵死态”;向父进程发送“子进程 终止(SIGCHLD)”信号;调用进程调度程序切换CPU的运行 进程。
若程序中不考虑fork()的返回值,则父子进程的行为就 完全一样了。但创建一个子进程的目的是想让它做另一件事。 所以,通常的做法是:在fork()调用后,通过判断fork()的返 回值,分别为父进程和子进程设计不同的执行分支。这样, 父子进程执行的虽是同一个代码,执行路线却分道扬镳。图 4-8描述了用fork()创建子进程的常用流程。
03_Linux进程控制_系统编程

LINUX进程控制1.Linux进程概述进程是一个程序一次执行的过程,是操作系统动态执行的基本单元。
进程的概念主要有两点:第一,进程是一个实体。
每个进程都有自己的虚拟地址空间,包括文本区、数据区、和堆栈区。
文本区域存储处理器执行的代码;数据区存储变量和动态分配的内存;堆栈区存储着活动进程调用的指令和本地变量。
第二,进程是一个“执行中的程序”,它和程序有本质区别。
程序是静态的,它是一些保存在磁盘上的指令的有序集合;而进程是一个动态的概念,它是一个运行着的程序,包含了进程的动态创建、调度和消亡的过程,是Linux的基本调度单位。
只有当处理器赋予程序生命时,它才能成为一个活动的实体,称之为进程。
内核的调度器负责在所有的进程间分配CPU执行时间,称为时间片(time slice),它轮流在每个进程分得的时间片用完后从进程那里抢回控制权。
1.1.进程标识OS会为每个进程分配一个唯一的整型ID,做为进程的标识号(pid)。
进程0是调度进程,常被成为交换进程,它不执行任何程序,是内核的一部分,因此也被成为系统进程。
进程除了自身的ID外,还有父进程ID(ppid)。
也就是说每个进程都必须有它的父进程,操作系统不会无缘无故产生一个新进程。
所有进程的祖先进程是同一个进程,它叫做init进程,ID为1,init进程是内核自举后的第一个启动的进程。
init进程负责引导系统、启动守护(后台)进程并且运行必要的程序。
它不是系统进程,但它以系统的超级用户特权运行。
1.2.进程的状态进程是程序的执行过程,根据它的生命周期可以划分成3种状态。
●执行态:该进程正在运行,即进程正在占用CPU。
●就绪态:进程已经具备执行的一切条件,正在等待分配CPU的处理时间片。
●等待态:进程不能使用CPU,若等待事件发生(等待的资源分配到)则可将其唤醒。
1.3.Linux下的进程结构及管理Linux系统是一个多进程的系统,它的进程之间具有并行性、互不干扰等特点。
Linux系统编程之进程控制(进程创建、终止、等待及替换)

Linux系统编程之进程控制(进程创建、终⽌、等待及替换)进程创建在上⼀节讲解进程概念时,我们提到fork函数是从已经存在的进程中创建⼀个新进程。
那么,系统是如何创建⼀个新进程的呢?这就需要我们更深⼊的剖析fork 函数。
1.1 fork函数的返回值调⽤fork创建进程时,原进程为⽗进程,新进程为⼦进程。
运⾏man fork后,我们可以看到如下信息:#include <unistd.h>pid_t fork(void);fork函数有两个返回值,⼦进程中返回0,⽗进程返回⼦进程pid,如果创建失败则返回-1。
实际上,当我们调⽤fork后,系统内核将会做:分配新的内存块和内核数据结构(如task_struct)给⼦进程将⽗进程的部分数据结构内容拷贝⾄⼦进程添加⼦进程到系统进程列表中fork返回,开始调度1.2 写时拷贝在创建进程的过程中,默认情况下,⽗⼦进程共享代码,但是数据是各⾃私有⼀份的。
如果⽗⼦只需要对数据进⾏读取,那么⼤多数的数据是不需要私有的。
这⾥有三点需要注意:第⼀,为什么⼦进程也会从fork之后开始执⾏?因为⽗⼦进程是共享代码的,在给⼦进程创建PCB时,⼦进程PCB中的⼤多数数据是⽗进程的拷贝,这⾥⾯就包括了程序计数器(PC)。
由于PC中的数据是即将执⾏的下⼀条指令的地址,所以当fork返回之后,⼦进程会和⽗进程⼀样,都执⾏fork之后的代码。
第⼆,创建进程时,⼦进程需要拷贝⽗进程所有的数据吗?⽗进程的数据有很多,但并不是所有的数据都要⽴马使⽤,因此并不是所有的数据都进⾏拷贝。
⼀般情况下,只有当⽗进程或者⼦进程对某些数据进⾏写操作时,操作系统才会从内存中申请内存块,将新的数据拷写⼊申请的内存块中,并且更改页表对应的页表项,这就是写时拷贝。
原理如下图所⽰:第三,为什么数据要各⾃私有?这是因为进程具有独⽴性,每个进程的运⾏不能⼲扰彼此。
1.3 fork函数的⽤法及其调⽤失败的原因fork函数的⽤法:⼀个⽗进程希望复制⾃⼰,通过条件判断,使⽗⼦进程分流同时执⾏不同的代码段。
实验三 Linux进程的创建与控制

printf(“child %d\n”,i);
lockf(1,0,0);/解锁/
}
else
{
while((p2=fork())= =-1);
if(p2= =0)
{
lockf(1,1,0);
for(i=0;i<20;i++)
printf(“son %d\n”,i);
lockf(1,0,0);
}
else
{
lockf(1,1,0);
for(i=0;i<20;i++)
printf(“daughter %d\n”,i);
lockf(1,0,0);
}
}
}
思考问题:
(1)系统是怎样创建进程的?
(2)当首次调用新创建进程时,其入口在哪里?
while((p1=fork())= =-1);
if(p1= =0)
for(i=0;i<20;i++)
printf(“Child %d\n”,i);
else
{
while((p2=fork() )= =-1);
if(p2= =0)
for(i=0;i<20;i++)
printf(“Son %d\n”,i)
else
for(i=0;i<20;i++)
printf(“Daughter %d\n”,i);
}
}
程序2:
#include <stdio.h>
#include <unistd.h>
main()
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
上机三:Linux进程控制1.目的(1)掌握系统调用fork(),exex(),exit()等实现进程创建;(2)掌握进程的终止方式(return、exit、_exit、abort);(3)掌握僵尸进程的产生和避免,以及wait,waitpid的使用;(4)了解守护进程的创建。
2.内容主要上机分析代码文件。
systemtest.c6-3.c6-4.c6-8.c6-9.c其他略。
3.步骤1)Linux进程的创建创建进程可以采用几种方式。
可以执行一个程序(这会导致新进程的创建),也可以在程序内调用一个fork 或exec来创建新进程。
fork 调用会导致创建一个子进程,而exec 调用则会用新程序代替当前进程上下文。
exec系列函数并不创建新进程,调用exec前后的进程ID是相同的。
exec函数的主要工作是清除父进程的可执行代码映像,用新程序的代码覆盖调用exec 的进程代码。
如果exec执行成功,进程将从新程序的main函数入口开始执行。
调用exec 后,除进程ID保持不变外,还有下列进程属性也保持不变。
(1)进程的父进程ID。
(2)实际用户ID和实际用户组ID。
(3)进程组ID、会话ID和控制终端。
(4)定时器的剩余时间。
(5)当前工作目录及根目录。
(6)文件创建掩码UMASK。
(7)进程的信号掩码。
与exec系统调用不同,system将外部可执行程序加载执行完毕后继续返回调用进程。
【例6.3】设计一个程序,用fork函数创建一个子进程,在子进程中,要求显示子进程号与父进程号,然后显示当前目录下的文件信息,在父进程中同样显示子进程号与父进程号。
/*6-3.c 将一个进程分为两个一样的进程,打印出进程的相关信息*/#include<stdio.h> /*文件预处理,包含标准输入输出库*/#include<stdlib.h> /*文件预处理,包含system、exit等函数库*/#include<unistd.h> /*文件预处理,包含fork、getpid、getppid函数库*/#include<sys/types.h> /*文件预处理,包含fork函数库*/int main () /*C程序的主函数,开始入口*/{pid_t result;result=fork(); /*调用fork函数,返回值存在变量result中*/int newret;if(result==-1) /*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/ {perror("创建子进程失败");exit(0);}else if (result==0) /*返回值为0代表子进程*/{printf("返回值是:%d,说明这是子进程!\n此进程的进程号(PID)是:%d\n此进程的父进程号(PPID)是:%d\n",result,getpid(),getppid());execl(“/bin/ls”,”ls”,”-l”,0); /*调用ls程序,显示当前目录下的文件信息*/ }else /*返回值大于0代表父进程*/【步骤1】设计编辑源程序代码。
[root@localhost root]#vi 6-3.c【步骤2】用gcc编译程序。
[root@localhost root]#gcc 6-3.c –o 6-3【步骤3】运行程序。
编译成功后,执行6-3,此时系统会出现运行结果,根据result的值,先显示Linux系统分配给子进程的进程号(PID)和父进程号(PPID),接着运行ls程序,显示当前目录下的文件信息。
再等待10秒钟后,显示父进程的进程号(PID)和父进程号(PPID)。
【步骤4】在6-3.c代码中改变:execl(“/bin/ls”,”ls”,”-l”,0); /*调用ls程序,显示当前目录下的文件信息*/ 替换为:printf("执行前的进程号(PID)是:%d\n",getpid()); /*显示输出进程号*/printf("执行前的父进程号(PPID)是:%d\n",getppid());/*显示输出父进程号*/execv(“6-1”, NULL); /*调用6-1程序*/执行后观察进程的PID和PPID是否有改变。
2)Linux进程的终止(1) 正常终止:(a) 在main函数内执行return语句,这等效于调用exit。
(b) 调用exit函数。
此函数由ANSIC定义,其操作包括调用各终止处理程序,然后关闭所有标准I/O流等。
(c) 调用_exit系统调用函数,此函数由exit调用。
(2) 异常终止:(a) 调用abort 。
(b) 由一个信号终止。
exit, _exit, _Exit 都是进程终止函数。
abort产生SIGABRT 信号。
非正常退出,即在程序碰到灾难性错误时强制退出。
由于是非正常退出,因此不会做其它任何操作。
return与exit的区别在进程操作中exit是结束当前进程或程序并把控制权返回给调用该程序或者进程的进程即父进程并告诉父进程该当前进程的运行状态,而return是从当前函数返回,如果是在main 函数中,main函数结束时隐式地调用exit函数,自然也就结束了当前进程。
return是语言级别的,它表示了调用堆栈的返回;而exit是系统调用级别的,它表示了一个进程的结束。
exit函数是退出应用程序,并将应用程序的一个状态返回给OS,这个状态标识了应用程序的一些运行信息。
在main函数里面return(0)和exit(0)是一样的,子函数用return返回;而子进程用exitint main () /*C程序的主函数,开始入口*/{pid_t result;result=fork(); /*调用fork函数,返回值存在变量result中*/if(result==-1) /*通过result的值来判断fork函数的返回情况,这儿先进行出错处理*/ {perror("创建子进程失败");exit(0);}else if (result==0) /*返回值为0代表子进程*/{printf("测试终止进程的_exit函数!\n");printf("目前为子进程,这一行我们用缓存!");_exit(0);}else /*返回值大于0代表父进程*/{printf("测试终止进程的exit函数!\n");printf("目前为父进程,这一行我们用缓存!");exit(0);}3)Linux的僵尸进程(wait/waitpid的使用)僵尸进程是指的父进程已经退出,父进程没有处理子进程的退出信息(包括子进程的返回值和其他的一些东西),使得已退出的子进程就成为僵尸进程Defunct ("zombie")。
僵尸进程只是在process table里有一个记录,没有占用其他的资源,除非系统的进程个数的限制已经快超过了,zombie进程不会有更多的坏处。
通过在父进程里增加一个wait/waitpid可以解决僵尸进程问题。
一般来说,当父进程fork()一个子进程后,它必须用wait() 或者waitpid() 等待子进程退出。
正是这个wait() 动作完全清除子进程退出后的信息。
【例题】设计一个程序,要求用户可以选择是否创建子进程,子进程模仿思科(Cisco)1912交换机的开机界面,以命令行的方式让用户选择进入,父进程判断子进程是否正常终止。
图1 算法流程/*6-8.c 创建进程(cisco菜单)*/#include<stdio.h> /*文件预处理,包含标准输入输出库*/#include<unistd.h> /*文件预处理,包含fork函数库*/#include<sys/types.h> /*文件预处理,包含fork、wait、waitpid函数库*/ #include<sys/wait.h> /*文件预处理,包含wait、waitpid函数库*/#include<stdlib.h> /*文件预处理,包含exit函数库*/void display0(); /*子程序声明*/void display1();void display2();int main () /*程序的主函数,开始入口*/{pid_t result;int status,select,num;void (*fun[3])(); /*利用函数指针建立三个子程序*/fun[0]=display0;fun[1]=display1;fun[2]=display2;printf("1.创建子进程\n2.不创建子进程\n请输入您的选择:");scanf("%d",&select);if(select==1) /*如果用户输入1,创建进程*/{result=fork(); /*调用fork函数创建进程,返回值存在变量result中*/if(result==-1){perror("创建进程出错");exit(1);}}if (result==0) /*子进程*/{printf("这是子进程(进程号:%d,父进程号:%d): ",getpid(),getppid());printf("进入思科(Cisco)1912交换机开机界面。
\n ");printf("1 user(s) now active on Management Console.\n");printf("\tUser Interface Menu\n");printf("\t[0] Menus\n");printf("\t[1] Command Line\n");printf("\t[2] IP Configuration\n");printf("Enter Selection:");scanf("%d",&num); /*运用函数指针,运行相应的子程序*/if(num>=0&&num<=2)(*fun[num])();exit(0);}else{waitpid(result,&status,0); /*父进程调用waitpid函数,消除僵尸进程*/printf("这是父进程(进程号:%d,父进程号:%d)\n ",getpid(),getppid());if(WIFEXITED(status)==0)printf("子进程非正常终止,子进程终止状态:%d\n", WIFEXITED(status));elseprintf("子进程正常终止,子进程终止状态:%d\n", WIFEXITED(status));exit(0);}}/*子程序部分*/void display0(){printf("您选择进入了菜单模式\n");}void display1(){printf("您选择进入了命令行模式\n");}void display2(){printf("您选择进入了IP地址配置模式\n");}【步骤1】:设计编辑源程序代码[root@localhost root]#vim 6-8.c【步骤2】:用gcc编译程序[root@localhost root]#gcc 6-8.c –o 6-8【步骤3】:运行程序[root@localhost root]#./6-81.创建子进程2.不创建子进程请输入您的选择:2这是父进程(进程号:5028,父进程号:4739)子进程非正常终止,子进程终止状态:0@再次运行程序[root@localhost root]#./6-81.创建子进程2.不创建子进程请输入您的选择:1这是子进程(进程号:5044,父进程号:5043): 进入思科(Cisco)1912交换机开机界面。