文章结束给大家来个程序员笑话:[M]
1.1 实验目标
加深对于进程并发执行观点的懂得。实际并发进程的创立和控制方法。
视察和体验进程的动态特性。
进一步懂得进程性命期期间创立、变换、撤销状态变换的过程。
把握进程控制的方法,了解父子进程间的控制和协作关系。
训练 Linux 系统中进程创立与控制有关的系统调用的编程和调试技术。
1.2 实验说明
1)与进程创立、执行有关的系统调用说明
进程可以通过系统调用 fork()创立子进程并和其子进程并发执行。
子进程初始的执行映像是父进程的一个复本。
子进程可以通过 exec()系统调用族装入一个新的执行程序。
父进程可以使用 wait()或 waitpid()系统调用等待子进程的结束并担任收集和清算子进程的退出状态。
- fork()系统调用语法:
#include <unistd.h>
pid_t fork(void);//int类型,Process ID _ Type 的缩写
for
k 胜利创立子进程后将返回子进程的进程号,不胜利会返回-1。
- exec 系统调用有一组 6 个函数,其中示例代码中引用了 execve 系统调用语法:
#include <unistd.h>
int execve(const char *path, const char *argv[], const char * envp[]);
参数注释:
path:要装入的新的执行文件的绝对路径名字符串;
argv[]:要传递给新执行程序的完整的命令参数列表(可认为空);
envp[]:要传递给新执行程序的完整的环境变量参数列表(可认为空)。
Exec执行胜利后将用一个新的程序取代原进程,但进程号不变。
它绝不会再返回到调用进程了。
如果 exec 调用失败,它会返回-1。
- wait() 系统调用语法:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid,int *status,int option);
参数注释:
status 用于保存子进程的退出状态。
pid 可认为以下可能值:
-1:等待所有 PGID 等于 PID 的绝对值的子进程;
1:等待所有子进程;
0:等待所有 PGID 等于调用进程的子进程;
>0:等待 PID 等于 pid 的子进程。
option 划定了调用 waitpid 进程的行为:
WNOHANG:没有子进程时当即返回;
WUNTRACED:没有报告状态的进程时返回;
wait 和 waitpid 执行胜利将返回终止的子进程的进程号,不胜利返回-1。
- getpid()系统调用语法:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
getpid 返回以后进程的进程号,getppid 返回以后进程父进程的进程号。
2) 与进程控制有关的系统调用说明
可以通过信号向一个进程发送消息以控制进程的行为。
信号是由中断或异常事件引发的,如:键盘中断、定时器中断、合法内存引用等。
信号的名字都以 SIG 开头,例如 SIGTERM、SIGHUP。
可以使用 kill -l 命令查看系统以后的信号集合。
信号可在任何时间发生,接收信号的进程可以对接收到的信号采用 3 种处置措施之一:
1.疏忽这个信号
2.执行系统默认的处置
3.捕捉这个信号做自定义的处置
信号从发生到被处置所经过的过程:
产 生 (generate)-> 挂 起 (pending)-> 派 送 (deliver)-> 部 署 (disposition) 或 忽 略(igore)
一个信号集合是一个 C 语言的 sigset_t 数据类型的对象,sigset_t 数据类型定义在<signal.h>中。
被一个进程疏忽的所有信号的集合称为一个信号掩码(mask)。
从程序中向一个进程发送信号有两种方法:调用 shell 的 kill 命令,调用 kill 系统调用函数。
kill 能够发送除杀死一个进程(SIGKILL、SIGTERM、SIGQUIT)以外的其他信号,
例如键盘中断(Ctrl+C)信号 SIGINT,进程暂停(Ctrl+Z)信号SIGTSTP 等等。
调用 Pause 函数会令调用进程的执行挂起直到一个恣意信号到来后再继续运行。
调用 sleep 函数会令调用进程的执行挂起睡眠指定的秒数或一个它可以响应的信号到来后继续执行。
每个进程都能使用 signal 函数定义自己的信号处置函数,捕捉并自行处置接收的除 SIGSTOP 和 SIGKILL 以外的信号。
以下是有关的系统调用的语法说明。
- kill 系统调用语法:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数说明:
pid:接
收信号的进程号
signal:要发送的信号
kill 发送胜利返回接收者的进程号,失败返回-1。
- pause 系统调用语法:
#include <unistd.h>
int pause(void);
pause 挂起调用它的进程直到有任何信号到达。
调用进程不自定义处置方法,则停止信号的默认处置。
只有进程自定义了信号处置方法捕获并处置了一个信号后,pause 才会返回调进程。
pause 老是返回-1,并设置系统变量 errno 为 EINTR。
- sleep 系统调用语法:
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
参数说明:
seconds 指定进程睡眠的秒数,如果指定的秒数到
了,
sleep 返回 0。
- signal 系统调用语法为:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:要捕捉的信号
handler:进程中自定义的信号处置函数名
signal 调用胜利会返回信号处置函数的返回值,不胜利返回-1,并设置系统变量 errno 为 SIG_ERR。
1.3 示例实验
以下实验示例程序应实现一个类似子 shell 子命令的功能。
它可以从执行程序中启动另一个新的子进程并执行一个新的命令和其并发执行。
1)ctrl+alt+T 打开一终端,新建一个文件夹,在该文件夹中新建名为 pctl.c 的C语言程序:
/*
* 文件名称: pctl.c
*
* 程序目标: 以下实验示例程序应实现一个类似子 shell 子命令的功能
* 可以从执行程序中启动另一个新的子进程并执行一个新的命令和其并发执行。
*/
#include "pctl.h"
int main(int argc, char *argv[]) {
int i;
int pid; //存放子进程号
int status; //存放子进程返回状态
char *args[] = { "/bin/ls", "-a", NULL }; //子进程要缺省执行的命令
/**
signal(int signum, sighandler_t handler);
参数说明:signum:要捕捉的信号;handler:进程中自定义的信号处置函数名
调用胜利:返回信号处置函数的返回值sighandler_t,
调用失败:返回-1,并设置系统变量 errno 为 SIG_ERR。
*/
signal(SIGINT, (sighandler_t) sigcat); //注册一个本进程处置键盘中断的函数
//SIGINT在signum.h文件中的定义如下:
// #define SIGINT 2 /* Interrupt (ANSI). */
//sigcat在pctl.h文件中的定义如下:
// printf("%d Process continue\n", getpid());
pid = fork(); //创立子进程并和其子进程并发执行
printf("pid = %d \n", pid);
//返回值: 若胜利调用一次则返回两个值,子进程返回0,父进程返回子进程ID
// 建立子进程失败
if (pid < 0) {
printf("创立子进程失败!!\n");
exit(EXIT_FAILURE);
}
// 子进程执行代码段
if (pid == 0) {
printf("我是以后进程: %d 号!\n", getpid()); //返回以后进程的进程号
printf("以后的父进程: %d 号!\n", getppid()); //返回以后进程父进程的进程号
//调用 Pause 函数会令调用进程的执行挂起直到一个恣意信号到来后再继续运行。
printf("我暂停了!等待键盘的中断信号唤醒!\n");
pause(); //暂停,等待键盘中断信号唤醒
//子进程被键盘中断信号唤醒继续执行
printf("我是进程: %d 号!已经唤醒!即将执行!\n", getpid()); //返回以后进程的进程号
if (argv[1] != NULL ) {
//如果在命令行上输入了子进程要执行的命令
//则执行输入的命令
printf("检测到输入了子进程要执行的命令!");
printf("子进程要执行的命令如下:");
for (i = 1; argv[i] != NULL ; i++)
printf("%s ", argv[i]);
printf("\n");
/**
int execve(const char *path, const char *argv[], const char * envp[]);
path:要装入的新的执行文件的绝对路径名字符串.
argv[]:要传递给新执行程序的完整的命令参数列表(可认为空).
envp[]:要传递给新执行程序的完整的环境变量参数列表(可认为空).
Exec执行胜利后将用一个新的程序取代原进程,但进程号不变,不会再返回到调用进程了。
如果 exec 调用失败,它会返回-1。
*/
//装入并执行新的程序
status = execve(argv[1], &argv[1], NULL );
} else {
//如果在命令行上没输入子进程要执行的命令
//则执行缺省的命令
printf("检测到没有输入子进程要执行的命令!\n");
printf("子进程要执行的命令如下:\n");
for (i = 0; args[i] != NULL ; i++)
printf("%s ", args[i]); //执行程序中定义的命令
printf("\n");
//装入并执行新的程序
status = execve(args[0], args, NULL );
}
}
//父进程执行代码段
if (pid > 0) {
printf("我是父进程: %d 号!\n", getpid()); //报告父进程进程号
if (argv[1] != NULL ) {
//如果在命令行上输入了子进程要执行的命令
//则父进程等待子进程执行结束
printf("%d 等待子进程结束!\n", pid);
/**
pid_t waitpid(pid_t pid,int *status,int option);
status:用于保存子进程的退出状态
pid可认为以下可能值:
-1:等待所有 PGID 等于 PID 的绝对值的子进程
1 :等待所有子进程
0 :等待所有 PGID 等于调用进程的子进程
>0:等待 PID 等于 pid 的子进程
option 划定了调用 waitpid 进程的行为:
WNOHANG:没有子进程时当即返回
WUNTRACED:没有报告状态的进程时返回
执行胜利将返回终止的子进程的进程号,不胜利返回-1。
*/
waitpid(pid, &status, 0); //等待子进程结束
printf("我的子进程结束了!status = %d \n", status);
} else {
//如果在命令行上没输入子进程要执行的命令
//唤醒子进程,与子进程并发执行不等待子进程执行结束,
/**
int kill(pid_t pid, int sig);
pid:接收信号的进程号
signal:要发送的信号
发送胜利返回接收者的进程号,失败返回-1。
*/
//kill能够发送除杀死一个进程(SIGKILL、SIGTERM、SIGQUIT)以外的其他信号
if (kill(pid, SIGINT) >= 0)
printf("%d 号进程 唤醒 %d 号子进程!\n", getpid(), pid);
printf("%d 号进程没有等待子进程结束!\n", getpid());
}
}
return EXIT_SUCCESS;
}
2)新建名为 pctl.h 的头文件:
/*
* 文件名称: pctl.h
*
* 程序目标: 以下实验示例程序应实现一个类似子 shell 子命令的功能
* 可以从执行程序中启动另一个新的子进程并执行一个新的命令和其并发执行。
*/
#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//进程自定义的键盘中断信号处置函数
typedef void (*sighandler_t)(int);
void sigcat() {
printf("%d 号进程继续运行!From:sigcat\n", getpid());
}
3) 建立项目管理文件,名为makefile便可:
head = pctl.h
srcs = pctl.c
objs = pctl.o
opts = -g -c
all: pctl
pctl: $(objs)
gcc $(objs) -o pctl
pctl.o: $(srcs) $(head)
gcc $(opts) $(srcs)
clean:
rm pctl *.o
4) 在终端直接输入 make ,使用makefile编译连接生成可执行的 pctl 程序。
注意,makefile文件必须名为makefile,注意大小写。
这样便编译连接生成可执行的 pctl 程序
5)在终端输入 ./pctl 执行 pctl 程序(注意进程号是动态发生的,所以每次执行都不相同)
以这一段不输入执行命令的输出为例:
我是以后进程 4113 号!
我的父进程是 4112 号!
我是父进程 4112!
唤醒 4113 号子进程!
4112 号进程没有等子进程结束!
4113 号进程结束!
4113 号子进程即将运行:
/bin/ls -a
. .. Makefile pctl pctl.c pctl.h pctl.o
以上程序的输出说明父进程 4112 创立了一个子进程 4113,子进程执行被暂停。
父进程向子进程发出键盘中断信号唤醒子进程并与子进程并发执行。
父进程并没有等待子进程的结束继续执行先行结束了。
此时的子进程成为了孤儿进程,不会有父进程为它清算退出状态了。
而子进程继续执行,它变成了列出以后目录所有文件名的命令 ls -a。
在完成了列出文件名命令之后,子进程的执行也结束了。
此时子进程的退出状态将有初始化进程为它清算。
6) 再次执行带有子进程指定执行命令的 pctl 程序:
可以看到这一次子进程仍然被挂起,而父进程则在等待子进程的完成。
为了检测父子进程是不是都在并发执行,请输入 ctrl+z 将以后进程放入后台。
并输入 ps 命令查看以后系统进程信息,显示如下:
可以看到以后系统中同时有两个叫 pctl 的进程,它们的进程号分别是 13882和 13883。
它们的状态都为:T,说明以后都被挂起。
13883 的父进程是13882,而13882 的父进程是 13830,也就是 bash-shell。
为了让 pctl 父子进程继续执行,请输入 fg 命令让 pctl 再次返回前台:
$ fg
./pctl /bin/ls -l
当初 pctl 父子进程从新返回前台。
我们可以通过键盘,发送键盘中断信号,来唤醒pctl 父子进程继续执行。
输入 ctrl+c,将会显示:
以上输出说明白:
子进程在捕捉到键盘中断信号后继续执行了指定的命令,按我们要求的长格式列出了以后目录中的文件名,
父进程在接收到子进程执行结束的信号后将清算子进程的退出状态并继续执行,
它报告了子进程的退出编码(0 表示子进程畸形结束),最后父进程也结束执行。
1.4 独立实验
需求:参考以上示例程序中建立并发进程的方法,编写一个多进程并发执行程序。
父进程首先创立一个执行 ls 命令的子进程然后再创立一个执行 ps 命令的子进程,
并控制ps 命令总在 ls 命令之前执行。
项目源码:
1)头文件os1.h:
/*
* 文件名称: os1.h
*
* 程序目标:一个多进程并发执行程序。
* 父进程首先创立一个执行 ls 命令的子进程。
* 然后再创立一个执行 ps 命令的子进程,并控制ps 命令总在 ls 命令之前执行。
*/
#include <sys/types.h>
#include <wait.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
//进程自定义中断信号处置函数
typedef void (*sighandler_t)(int);
void sigcatch() {
printf("%d 号进程继续运行!From:sigcat\n", getpid());
}
2)主程序os1.c:
/*
* 文件名称: os1.c
*
* 程序目标:一个多进程并发执行程序。
* 父进程首先创立一个执行 ls 命令的子进程。
* 然后再创立一个执行 ps 命令的子进程。
* 并控制 ps 命令总在 ls 命令之前执行。
*/
#include "os1.h"
int main(int argc, char *argv[]) {
int i;
int pid_ls; //存放子进程号
int pid_ps; //存放子进程号
int status_ls; //存放子进程返回状态
int status_ps; //存放子进程返回状态
char *args_ls[] = { "/bin/ls", "-a", NULL }; //ls子进程要缺省执行的命令
char *args_ps[] = { "/bin/ps", "-l", NULL }; //ps子进程要缺省执行的命令
signal(SIGINT, (sighandler_t) sigcatch); //注册一个本进程处置键盘中断的函数
pid_ls = fork(); //创立子进程并和其子进程并发执行
printf("pid_ls = %d \n", pid_ls);
// 建立 ls 进程失败
if (pid_ls < 0) {
printf("创立 pid_ls 进程失败!!\n");
exit(EXIT_FAILURE);
}
// ls 子进程执行代码段
if (pid_ls == 0) {
printf("我是以后进程(ls): %d 号!\n", getpid()); //返回以后进程的进程号
printf("以后的父进程(ls): %d 号!\n", getppid()); //返回以后进程父进程的进程号
//调用 Pause 函数会令调用进程的执行挂起直到一个恣意信号到来后再继续运行。
printf("我(ls)暂停了!等待键盘的中断信号唤醒!\n");
pause(); //暂停,等待中断信号唤醒
//子进程被键盘中断信号唤醒继续执行
printf("我是ls进程: %d 号!已经唤醒!即将执行!\n", getpid()); //返回以后进程的进程号
printf("ls 子进程要执行的命令如下:\n");
for (i = 0; args_ls[i] != NULL ; i++)
printf("%s ", args_ls[i]); //执行程序中定义的命令
printf("\n");
//装入并执行新的程序
status_ls = execve(args_ls[0], args_ls, NULL );
printf("status_ls = %d \n", status_ls);
}
// ls 父进程执行代码段
if (pid_ls > 0) {
pid_ps = fork(); //创立 ps 子进程并和其子进程并发执行
printf("pid_ps = %d \n", pid_ps);
// 建立 ps 子进程失败
if (pid_ps < 0) {
printf("创立 pid_ps 进程失败!!\n");
exit(EXIT_FAILURE);
}
// ps 子进程执行代码段
if (pid_ps == 0) {
printf("我是以后进程(ps): %d 号!\n", getpid()); //返回以后进程的进程号
printf("以后的父进程(ps): %d 号!\n", getppid()); //返回以后进程父进程的进程号
printf("ps 子进程要执行的命令如下:\n");
for (i = 0; args_ps[i] != NULL ; i++)
printf("%s ", args_ps[i]); //执行程序中定义的命令
printf("\n");
//装入并执行新的程序
status_ps = execve(args_ps[0], args_ps, NULL );
printf("status_ps = %d \n", status_ps);
}
// ps 父进程执行代码段
if (pid_ps > 0) {
printf("%d 等待 ps 子进程结束!\n", pid_ps);
waitpid(pid_ps, &status_ps, 0); //等待子进程结束
printf("我的 ps 子进程结束了!status = %d \n", status_ps);
if (kill(pid_ls, SIGINT) >= 0) {
printf("%d 号进程 唤醒 %d 号子进程!\n", getpid(), pid_ls);
} else {
printf("%d 号进程 唤醒 ls 进程失败!\n", getpid());
}
}
}
return EXIT_SUCCESS;
}
3)运行效果:
1.5. 实验要求
文章结束给大家分享下程序员的一些笑话语录: 刹车失灵
有一个物理学家,工程师和一个程序员驾驶着一辆汽车行驶在阿尔卑斯山脉 上,在下山的时候,忽然,汽车的刹车失灵了,汽车无法控制地向下冲去, 眼看前面就是一个悬崖峭壁,但是很幸运的是在这个悬崖的前面有一些小树 让他们的汽车停了下来, 而没有掉下山去。 三个惊魂未定地从车里爬了出来。
物理学家说, “我觉得我们应该建立一个模型来模拟在下山过程中刹车片在高 温情况下失灵的情形”。
工程师说, “我在车的后备厢来有个扳手, 要不我们把车拆开看看到底是什么 原因”。
程序员说,“为什么我们不找个相同的车再来一次以重现这个问题呢?”
--------------------------------- 原创文章 By
进程和子进程
---------------------------------