今天终于看完了令人头大的进程部分的知识,将学习过程中遇到的一些问题总结如下。
一、为什么给父进程和子进程分别加上sleep(1)语句就能够让父子进程交替执行?
在本章内容的第一节有一段代码(代码一)讲的是用fork()函数来创建一个子进程然后发现父子进程的先后执行顺序是不确定的,不同地方和时间执行顺序可能会不一样,而在下一小节内容中有出现了一段代码(代码二)发现父子进程的执行成了交替执行的,而令其交替执行的代码不是多么特别高大上的语句,竟然仅仅是分别在父子进程中多了sleep(1)这条语句。(废话不说贴代码):
代码一:
<span style="font-size:18px;">#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int i;
pid_t pid;
pid = fork();
switch(pid)
{
case 0:
for (i = 0; i < 10; i++)
printf("子进程中!\n");
exit(0);
case -1:
printf("创建失败!\n");
exit(-1);
default:
for (i = 0; i < 10; i++)
printf("父进程中!\n");
}
return 0;
}
</span>
代码二:
<span style="font-size:18px;">#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
int i;
pid_t pid;
pid = fork();
switch(pid)
{
case 0:
for (i = 0; i < 10; i++)
{
printf("子进程中!\n");
sleep(1);
}
exit(0);
case -1:
printf("创建失败!\n");
exit(-1);
default:
for (i = 0; i < 10; i++)
{
printf("父进程中!\n");
sleep(1);
}
}
return 0;
}
</span>
为什么会出现这种情况呢?google查资料可知,在操作系统的进程调度中有一种叫做时间片轮转调度算法,名字非常高大上,其实就是给每个进程分配一小段时间来执行自己的任务,超时后再去执行其它进程,这样可以最大限度的发挥处理器的性能,充分利用处理器在同样时间内做更多的事情。
代码一中没有加sleep(1),这就让两个进程很尴尬,受到操作系统进程调度算法的不同和不同时间执行进程时间长短的不同就出现了不同时间不同地点进程的执行顺序可能不一样的情况。在代码二中,假设现在正在执行父进程,当父进程执行到sleep(1)后暂停,此时时间片轮转调度算法就起作用了,操作系统将执行权交给子进程以保证系统资源不被浪费,这样就开始执行子进程了,可是子进程中也有一个sleep(1),当执行到sleep(1)后子进程也同样暂停了,因为计算机执行一条语句的时间非常短,短到子进程执行到sleep(1)语句时父进程还在暂停中,所以这就出现了父子进程交替执行一次暂停一秒,再交替执行一次的现象。
二、守护进程的创建
守护进程是指在后台运行的、没有控制终端与之相连的进程,守护进程独立于控制终端,通常周期性地执行某些任务,linux的大多数服务器就是用守护进程实现的,守护进程在后台执行,类似于windows中的系统服务。创建一个守护进程的步骤分为如下几步:
(1)、创建子进程,令父进程退出
(2)、在子进程中创建新会话(使用setsid让其成长为会话组长)
(3)、改变当前工作目录为根目录(chdir("/"))
(4)、重设(清除)文件权限码(umask(0))
(5)、关闭文件描述符(用循环从0开始一直关到当前打开的最大文件描述符)
(6)、守护进程退出处理(将SIGCHLD信号设为SIG_IGN)
第(2)的作用在于创建的进程摆脱父进程的控制终端、登陆会话等的影响。第(3)步的目的在于让创建进程时的目录可卸载(因为工作目录不可卸载),减少后续发生错误的几率。第(4)步的目的在于让文件权限恢复初始状态,让守护进程的执行不会受到父进程设置文件权限码后带来的麻烦。第(5)步的目的在于让父进程打开的文件可卸载(打开的文件不能卸载),让守护进程更灵活。第(6)步的目的是防止守护进程变成僵尸进程。
三、进程执行新程序(exec函数族)
使用fork()和vfork()创建子进程后通常是为了让子进程执行其它程序,系统调用exec函数族的函数来让进程执行其它程序。exec函数族有六个函数,调用这些函数不会创建新的进程,它们的作用只是将进程原有的数据段和堆栈段废弃掉,用新程序的数据段和堆栈段来替代,唯一保留该进程的ID。对系统而言虽然是同一个进程但是执行的已经是另外一个程序了,用一个程序来总结这六个函数和注意事项吧。
exec函数族总结:
/*
注意:父进程必须调用wait()或者waitpid()等待子进程结束,防止子进程成为僵尸进程
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int a;
char *envp[] = {"ls","-l",NULL};
extern char **environ;
pid = vfork();
switch(pid)
{
case 0:
// printf("哈哈!\n");
if (execl("/bin/ls","ls","-l",NULL) == -1) //execl()函数练习,第一个参数为路径,其余为程序参数
// if (execlp("ls","ls","-la",NULL)) //execlp()函数练习,会在PATH中寻找第一个参数为名称的程序,其余为被调程序的参数
// if (execv("/bin/ls",envp) == -1) //execv()函数练习,第一个参数为路径,其余为被调程序的参数
// if (execve("/bin/ls",envp,environ) == -1) //execve()函数练习,第一个参数为路径,第二个为完整参数列表,第三个为新环境变量
// if (execle("/bin/ls","ls","-l",NULL,environ) == -1) //execle()函数练习,第一个参数为路径,第一个NULL之前为程序参数列表,其后的为新环境变量
// if (execvp("ls",envp) == -1) //execvp()函数练习,会在PATH中寻找第一个参数为名称的程序,第二个为被调函数的参数列表
perror("ERROR");
exit(0);
case -1:
perror("vfork error"); break;
default:
// waitpid(pid,&a,0); //父进程等待子进程运行结束,第一个参数为等待的子进程ID,第二个存放错误码,第三个标识父进程的动作
wait(&a); //父进程等待子进程运行结束,参数中存放子进程结束后的错误码(main函数中的返回值或者exit()函数的参数)
printf("父进程中!\n");
}
return 0;
}