(声明:本篇博客只是博主自己的理解,加以整理,目的是总结刚学过的进程知识,不一定绝对正确,非常愿意听客官您提出宝贵意见。)
Q1:进程中的全局数据段(全局变量),局部数据段(局部变量),静态数据段的分别位于哪个内存地址空间?
答:对于进程的概念,我把它理解一个可执行程序进行的实体,我们c语言代码在变成可执行文件的过程中都会经历下面4步(以我们大家接触的第一个c语言程序“helloworld”为例)
(1)预处理:将中的内容和一些宏定义展开,将if,else等判断处理,将注释转化为空格等,我们可以执行 gcc -E helloworld.c -o helloworld.i 来获取预处理之后的文件,在hellowrold.i文件末尾,我们可以找到我们短短的几行程序。
(2)编译:编译的过程主要是编译器对我们代码的语法,词法分析,我们编译器的报错机制就是这部分的体现。同样,我们可以执行 gcc -S helloworld.c -o helloworld.s 同样我们可以看到它的汇编指令(编译的结果得到的是汇编指令)。当然我不懂汇编指令。就不多说了。
(3)汇编:汇编的过程是将我们的汇编指令转化为我们的计算机能够识别的机器指令。
(4)链接:生成我们的可执行文件的过程,可执行的概念是可以被加载或者可以被拷贝到存储器中等待执行。
如果我们运行它,它就会成为一个进程,程序到进程大概是这个过程,首先内核将程序读入内存,并且内存为其分配相应的内存空间,然后内核为其分配pid,和其他所需资源,接着内核会保存pid以及相应的状态信息,并且把进程放到运行队列里等待执行,要是被操作系统调度使用,进程就建立。下来我们进入我们要回答的问题:进程的内存映像是指程序在内存中的状态,也可以说在内存中如何存放的。linux系统的内存映像一般是这样,从内存的地址由低到高依次为:代码段,静态数据段(以被初始化的变量,全局变量和静态变量),动态数据段(malloc),堆段,栈段,命令行参数和环境量。
Q2:列举子进程的父进程的异同。
答:fork()函数是我们用户能唯一使用的创建进程的方式,在fork()和vfork()中,子进程和父进程的特点是不同的:
子进程和父进程相同点 子进程和父进程不同点
fork() 子进程继承父进程的下列信息:
1)用户ID,组ID,当前工作目录,根目录,打开的文件
2)创建文件时使用的屏蔽字,信号屏蔽字,上下文环境,等 1)有它自己的pid
2)不同的父进程ID
3)子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。
4)子进程不继承父进程设置的文件锁和警告。
vfork()
与fork()不同 同样子进程会继承这些父进程的信息 除了上面说的之外,补充一点:
vfork()之后并不会将父进程的地址空间完全复制给子进程,子进程会在父进程的地址空间上运行,这一点和线程的概念是相似的
Q3:如何创建一个后台进程(守护进程)?
答:对于守护进程的概念,我自己认为linux下最简单的例子就是服务了,比如我们安装vsftp服务,并且给它设置enable(开机自动启动),当我们的系统启动之后,它就已经正常工作了,只要我们不关机,它就一直在后台运行,它是独立于我的控制终端的;还有我们要把自己的一块磁盘挂载到某个mountpoint上,但是每次自己手动挂载都很麻烦,我们就可以设置开机自动挂载,vim /etc/fstab 文件,再次开机之后,它就已经挂载好了。我们的计算机要是要提供某种服务,就必须会创建守护进程。步骤大概有5步
void create_daemon();
void create_daemon()
{
int pid;
int i;
pid = fork(); 1 //先让父进程fork一个进程
if(pid > 0) //结束父进程,让子进程脱离当前进程独立
exit(0);
setsid(); 2 //设置子进程为会话组组长.
pid = fork(); 3 //再次fork一个子进程,并让父进程退出。
if(pid > 0) 4 //禁止进程重新打开控制终端,现在进程已经成为一个无终端的会话组组长,但是它可以申请来打 开一个终端,,为了避免这种情况,可以通过进程不再是会话组组长实现,所以再次fork;
exit(0);
for(i = 0;i < NOFILE;close(i++)) 5 //关闭继承的父进程的所有打开的文件描述符
;
umask(0); //将文件创建时屏蔽字设置为零(权限设置为777),因为从父进程继承的东西可能屏蔽掉某种权限
chdir("/"); //当守护进程的工作目录在一个装配文件目录中时,它不可卸载,一般需要将根目录设置为工作目录
signal(SIGCHLD,SIG_IGN); //这样可以保证子进程不会产生僵尸进程。
}
int main(int argc,char *argv[])
{
printf("create a daemon\n");
create_daemon();
while(1) //守护进程我们通常让它周期的执行某种任务,所以我们会让它在while(1)循环中执行
{
sleep(1);
}
}
进程组:一个进程组中包含好几个进程或者一个进程,拥有进程组ID,进程组ID就是进程组组长的ID。
会话组:会话组是一个或者几个进程组的集合,通常一个会话组开始于一个用户的登录,结束于用户的退出,
setsid()作用:让进程摆脱原进程组,会话组,以及终端的控制。
Q4:在多进程中,父子进程的运行顺序是怎样的。
答:fork之后的函数,无论多进程还是单进程,父进程与子进程的运行顺序是不确定的,由内核的调度算法来确定。
vfork之后的函数就可以保证子进程先执行。具体代码可以浏览我的这篇博客:http://blog.chinaunix.net/uid-30131847-id-5137459.html
Q5:有一个全局变量 i 的初值为5,父进程对其进行加1操作,子进程看到的 i 的值是多少?
答:对于这个问题,fork()和vfork()得到的结果是不一样的。
int globvar = 6;
int main(int argc,char *argv[])
{
int pid;
int var = 5;
printf("now var is %d ,globvar is %d\n",var,globvar);
// pid = fork();
pid = vfork();
switch(pid)
{
case 0:
var++;
globvar++;
printf("child process var is %d, globvar is %d\n",var,globvar);
exit(0);
default:
var++;
globvar++;
printf("father process var is %d, globvar is %d\n",var,globvar);
exit(0);
}
exit(0);
}
fork的执行结果:
now var is 5 ,globvar is 6 //fork函数子进程继承父进程的全局和局部变量之后,分别执行各自的代码,
father process var is 6, globvar is 7 //所以因为初始变量相同,执行的程序也相同,他们的值也相同
child process var is 6, globvar is 7
vfork的执行结果: //vfork函数保证子进程先执行,由于它在父进程地址上运行,所以它先对两个变量的改变对于
now var is 5 ,globvar is 6 //父进程是可见的,子进程运行完之后,父进程得到已经被改变一次的变量值继续运行。
child process var is 6, globvar is 7
father process var is 7, globvar is 8
fork()函数的一瞬间全局变量和局部变量已经被子进程继承,以后他们运行在不同的地址空间,父进程更改,对于子进程是不可见的。
vfork()函数虽然子进程运行在父进程的地址空间上,但是vfork()保证了子进程的优先运行,应此父进程更改全局变量,子进程看不到。