实验要求:
学习和了解进程控制的基本和常用的系统调用 fork wait sleep exit exec 等等。
查看 /usr/src/include/sched.h中的task_struct 数据结构,并分析Linux 操作系统进程状态。
通过进程创建的应用实例,深刻理解进程创建的过程。
思考题:
1、当首次调用新创建进程时,其入口在哪里?
2、可执行文件加载时进行了哪些处理?
3、什么是进程同步?wait( )是如何实现进程同步的?
实验一
1、修改源程序
如果按实验指导代码运行你,你会发现报错:
file.c:23:1: note: include ‘<stdlib.h>’ or provide a declaration of ‘exit’
因此需要填上头文件 include <stdlib.h>
2、正确的代码如下
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h> //实验指导上少了这一个头文件
int main(void)
{
pid_t pid;
int data=5;
if((pid=fork())<0)
{
printf("fork error\n");
exit(0);
}
else if(pid==0)
{
data--;
printf("child\'s data is:%d\n",data);
exit(0);
}
else
{
printf("parent\'s data is:%d\n",data);
}
exit(0);
}
3、运行的流程
详见我上一篇博文,使用vi运行c文件
4、fork()函数函数注明
为什么明明是if-else,却输出两个结果
这是因为
fork()函数
1、在Linux中从已存在的进程中创建一个新的进程唯一方法是使用fork()或vfork(),,原来存在的进程为父进程,新创建的进程为子进程,fork()函数执行一次返回两个值,子进程返回0,父进程返回子进程的PID,出错返回-1;使用fork()创建的子进程就是父进程的一个复制品,子进程继承了整个进程的地址空间,包括进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等,而子进程所独有的只有它的进程号、资源使用和计时器等。
2、使用fork()创建子进程后,在程序运行时,子进程和父进程同时运行,同时!而,fork()函数执行一次返回两个值,子进程返回0,父进程返回子进程的PID,出错返回-1;因此子进程运行时pid=0,输出“child”,父进程时,pid>0,输出“parent”
3、程序流程:
4、Linux pid_t 类型的定义,具体可以查考这篇博文,结论即,pid_t就是int。
实验二
1、修改源程序及wait()函数说明
按实验指导运行,报错
故修改如下
1、由于使用wait()函数需要头文件
#include <sys/types.h>/* 提供类型pid_t的定义*/
#include <sys/wait.h>
故添加
#include <sys/wait.h>
2、wait()函数功能是:
父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
原型int wait(int *status)
参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:pid = wait(NULL);
如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。
故将wait()修改为pid = wait(NULL);
2、正确代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h> //实验指导上少了这一个头文件"#include <sys/wait.h> //实验指导上少了这一个头文件
void main( )
{
pid_t pid;
char *path="/bin/ps";
char *argv[5]={ "ps","-a","-x",NULL};
printf("Run ps with execve by child process:\n");
if((pid=fork( ))<0)
{
printf("fork error!");
exit(0);
}
else if (pid==0)
{
if(execve(path,argv,0)<0)
{
printf("fork error!");
exit(0);
}
printf("child is ok!\n");
exit(0);
}
//wait( );
pid = wait(NULL); //实验指导上 wait 用错了
printf("it is ok!\n");
exit(0);
}
3、运行过程及结果
4、函数注明:exec函数族及exit()/_exit()
1、exec函数族
exec函数主要用于在进程中启动另一个程序执行的方法,exec函数族是6个以exec字母开头的函数,这6个函数执行成功不返回,只有出错时返回-1,它们就是:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const
envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *
下面对这些函数参数进行说明:
(1)通过对比这些函数,发现只有函数名的第五和第六位的字母不一样,首先看不带字母“p”的函数,第一个参数必须是可执行程序的绝对路径或相对路径,而不能是文件名,这个路径就是PATH环境变量的值;
(2)带有字母“l ”(表示list )的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有… ,… 中的最后一个可变参数应该是NULL,起sentinel的作用;
(3)带有字母“v”(表示vector )的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样;
(4)以“e” (表示environment )结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。
简而言之,该函数就是用指定程序覆盖当前程序代码,启动另一个程序。因此本文中由其调用execve启动shell命令ps查看系统当前的进程信息。
2、exit()/_exit()
exit()和_exit()都是用来无条件中止进程,在调用exit()比exit()时多做了些清理、保存工作,最后执行exit()中止进程执行;而_exit()则是直接执行exit()中止进程执行。对于由exit()结束的函数无论你函数原型是void 或者int都可以运行
思考题
1、当首次调用新创建进程时,其入口在哪里?
答:
程序流程:
2、可执行文件加载时进行了哪些处理?
将源代码转换为机器可认识代码的过程。编译程序读取源程序(字符流),对之进行词法
和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,
并且按照操作系统对可执行文件格式的要求链接生成可执行程序。具体经过以下几个处理:
C源程序一>编译预处理一>编译一>优化程序一>汇编程序一>链接程序一>可执行文件
3、什么是进程同步?wait( )是如何实现进程同步的?
进程同步是指对多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有
效地共享资源和相互合作,从而使程序的执行具有可在现行。
首先,程序在调用fork()机那里了一个子进程后,马上调用wait(),使父进程在子进程调
用之前一直处于睡眠状态,这样就使子进程先运行,子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行,实现进程同步。
参考链接:
https://blog.csdn.net/wuqishuang/article/details/48399251
https://www.cnblogs.com/kazama/p/10734951.html
https://bbs.csdn.net/topics/350133461
https://www.cnblogs.com/kazama/p/10734962.html