进程控制学习笔记

                             



            0.进程与程序(process & program)

                     进程简单的讲就是运行中的程序,它是一个动态的实体,是程序的一次执行过程。进程与程序的区别在于进程是

                     动态的,程序是静态的,进程是运行中的程序,程序是一些保存在硬盘上的可执行的代码。所以很多人也说,进

                     程是活的,程序是死的。linux下可通过命令ps aux或top查看当前系统中的进程。



            1.进程的内存映像

                    当一个程序执行时,操作系统将可执行程序复制到内存中,程序转化为进程要经过以下3个步骤:

                      *内核将程序读入内存,为程序分配内存空间;

                      *内核为该进程分配进程标识符(pid)和其他所需资源;

                      *内核为该进程保存pid级相应的状态信息,把进程放到运行队列中等待执行。程序转化为进程后就可以被操作系统

                        的调度程序调度执行了。


                   进程的内存映像指内核在内存中如何存放可执行程序文件。这里的可执行程序与内存映像的区别在于:

                       *可执行程序位于磁盘中,内存映像位于内存中;

                       *可执行程序没有堆栈,只有程序被加载到内存中才会分配堆栈;

                       *可执行程序是静态的、不变的,内存映像随着程序的执行是在动态变化的,因为数据是随着运行过程改变的。




             2.fork 与vfork

                  linux下创建新进程的系统调用函数是fork,fork翻译成中文是“分叉”的意思,顾名思义,当一个进程运行时调用了fork函  

                  数,那么它就会产生一个新的进程,也就是“分叉”了。


                  fork是一个很特殊的函数,特殊在它有两个返回值,也就是说,调用一次fork,返回两次。因为当我们成功调用fork之后,

                  当前进程已“分叉”为两个进程。一个是原来的父进程,另一个是刚刚创建的子进程。当调用失败时(父进程拥有的子进程

                  个数超过了规定限制,或者可供使用的内存不足),fork返回-1;成功时,对于父进程,返回的是子进程的pid,对于

                  进程,返回的是0。(我们可以用getpid(),getppid()分别获取当前进程pid和当前进程父进程pid)。

                  一般来说,fork之后是父进程先执行还是子进程先执行是不确定的,这取决与内核所使用的调度算法。但因为操作系统一

                  般让所有进程都享有同等执行权,所以父子进程一般都是交替执行的

             

                  当调用fork之后,系统让新进程与旧进程使用同一段代码,因为它们的程序还是相同的,同时其堆栈段数据段,系统会复

                  制一份给子进程,这样,子进程就几乎继承了父进程的全部属性,但是,子进程有它自己唯一的pid,而子进程一旦开

                 始运行,虽然它继承了父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再

                 共享任何数据了。那么,如果一个程序很庞大,fork一次就复制一次,系统开销岂不是很大?其实,一般CPU都是以“页”为

                 单位而无论是数据段还是堆栈段都是由许多“页”构成的,fork函数复制这两个段时,其实只是“逻辑”上的复制,并非“物理”上

                 的,也就是说,实际执行fork时,物理空间上两个进程的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,

                 这时两个进程之间的数据才有了区别,系统就将有区别的“页”从物理上也分开,这样系统在空间上的开销就可以达到最小    

               (写时拷贝)。


                  vfork也可以用来创建一个新进程,同样也是调用一次,返回两次。但注意,使用fork时,子进程完全复制父进程的资

                  源,但使用vfork创建的子进程共享父进程的地址空间,也就是说子进程完全运行在父进程的地址空间上。子进程对该

                  地址空间中任何数据的修改同样为父进程所见。同时,vfork保证子进程先运行,在子进程调用exit或exec之前父进程处

                  于阻塞等待状态。

                  为了区别fork与vfork,我们来看一个例子:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

 int globVar = 5;

int main(void)
{
	pid_t  pid;
	int    var = 1, i;
	
	printf("fork is diffirent with vfrok \n");

//	pid = fork();
	pid = vfork();
	switch(pid) {
		case 0:
			i = 3;
			while(i-- > 0)
			{
				printf("Child process is running\n");
				globVar++;
				var++;
				sleep(1);
			}
			printf("Child's globVar = %d,var = %d\n",globVar,var);
			exit(0);
		case -1:
			perror("Process creation failed\n");
			exit(0);
		default:
			i = 5;
			while(i-- > 0)
			{
				printf("Parent process is running\n");
				globVar++;
				var++;		
				sleep(1);
			}
			printf("Parent's globVar = %d ,var = %d\n", globVar ,var);
			exit(0);
	}
}

                 

                      上述程序,当我们使用fork时,结果如下:

                 

                         当我们注释掉fork,使用vfork时,结果如下:

                   

                由此我们可以看出,fork继承了父进程的全局变量和局部变量,但fork的子进程有自己独立的地址空间,不管是

                全局变量还是局部变量,子进程与父进程对他们的修改互不影响。vfork创建子进程后,父进程中的globvar和var

                最后均递增了8,这是因为vfork的子进程共享父进程的地址空间,子进程修改变量对父进程是可见的。从运行结

                果还可以看出,vfork保证了子进程先运行。



                  3.进程退出

                       *exit函数:exit(int exit_code),在stdlib.h中声明,exit中的参数若为0则代表正常终止,非0则异常终止。exit()

                                           要先执行一些清除操作,然后将控制权交给内核。

                       *_exit函数: 在unistd.h中声明,执行后立即返回给内核。        

                       *return:是函数执行完后的返回,将控制权交给调用函数。如果在main()中使用return语句,就相当于exit()。

                       *about:异常终止。

    不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为进程关闭所有打开的文件描述符,释放它所使用的存储器等。


                    

                4.执行新程序

                    使用fork或vfork创建子进程后,子进程通常会调用exec函数来执行另外一个程序。也就是说系统调用exec函数用于执行

                    一个可执行程序以代替当前进程的执行映像。linux下,exec函数族有以下6中不同的调用形式。(声明在<unistd.h>

                    中)

                      int execl(const char *path, constchar *arg, ...)
                      int execv(const char *path, char *constargv[])
                     int execle(const char *path, const char *arg,..., char *const envp[])
                      int execve(const char *path, char *const argv[],char *const envp[])

 
                   int execlp(const char *file,const char *arg, ...)
                     int execvp(const char *file, char *constargv[])

                   注意:

                     exec调用并没有生成新进程,一个进程一旦调用exec函数,它本身就“死亡”了,系统把代码段替换成新的程序的代码

                    废弃原有的数据段和堆栈段,并为新程序分配新的数据段和堆栈段唯一保留的就是进程ID。也就是说,对系统而

                    言,还是同一个进程,不过执行的已经是另一个程序了。所以正常情况下,这些函数是不会返回的,因为进程的执行映

                    像已经被替换,没有接收返回值的地方了。


                     5.wait与waitpid

                   #include <sys/types.h>
                       #include <sys/wait.h>
                       pid_t wait (int &status);
                       pid_t waitpid (pid_t pid, int *statloc, int options);  


                       当子进程先于父进程退出时,如果父进程没有调用wait或waitpid函数,子进程就会进入僵死状态,如果父进程调用了

                       wait或waitpid函数,子进程就不会变成僵尸进程。


                      进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果它找到了这样一

                       个子进程, wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻

                       塞在这里,直到有一个出现为止。参数status用来保存所指向的变量的存放子进程的退出码,它是一个指向int类型

                       的指针。但如果我们对这个子进程是如何死掉的毫不在意,只是想把这个进程消灭掉,我们就可以这样写:

                       pid = wait(NULL)。如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时

                       wait返回-1。

                       waitpid也用来等待子进程的结束,但它多了两个参数,这两个参数使他可以等待某个特定进程的结束。参数pid指明

                      要等待的子进程的pid。当pid>0时,只等待进程ID等于pid的子进程退出,不管其它已经有多少子进程运行结束退出了,

                      只要指定的子进程还没有结束,waitpid就会一直等下去;pid=-1时,等待任何一个子进程退出,没有任何限制,此时

                      waitpid和wait的作用一样。即wait(&status) 等价于waitpid(-1, &status, 0); pid=0时,等待同一个进程组中的任何子进

                      程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬; pid<-1时,等待一个进程组的ID等于pid的绝对值

                      的任一子进程退出。options参数目前在Linux中只支持WNOHANG和WUNTRACED两个选项,如果我们不想使用它

                      们,也可以把options设为0。有时希望取得一个子进程的状态,但不想使父进程阻塞,那么就可以使用WNOHANG参数

                      调用waitpid,表示即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。







             


        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值