进程是一个动态的实体,是程序的一次执行过程。进程是操作系统资源分配的基本单位。
以下是一些概念,我就直接抄书了
进程是操作系统的知识,简单理解的话,你写的代码运行起来算一个进程?
创建进程
每个进程由进程ID号标识,进程被创建时系统会为其分配一个惟一的进程ID。当一个进程向其父进程(创建该进程的进程)传递其终止消息时,意味这个进程的整个生命周期的结束。此时,该进程占用的所有资源包括进程ID被全部释放。
创建进程有两种方式,一是由操作系统创建;二是由父进程创建。由操作系统创建的进程,它们之间是平等的,一般不存在资源继承关系。而对于由父进程创建的进程(通常称为子进程),它们和父进程存在隶属关系。子进程又可以创建进程,这样形成一个进程家族。子进程可以继承其父进程几乎所有的资源。在系统启动时,操作系统会创建一些进程,它们承担着管理和分配系统资源的任务,这些进程通常被称为系统进程。
系统调用fork是创建一个新进程的惟一方法(创建一个进程通常也称为fork一个进程),除了极少数以特殊方式创建的进程,如 init进程,它是内核启动时以特殊方式创建的。进程调用fork函数就创建了一个子进程。
创建了一个子进程之后,父进程和子进程争夺CPU,抢到CPU 者执行,另外一个挂起等待。如果想要父进程等待子进程执行完毕以后再继续执行,可以在 fork 操作之后调用 wait或waitpid。一个刚刚被fork的子进程会和它的父进程一样,继续执行当前的程序。几个进程同时执行一个应用程序通常用处不大。更常见的使用方法是子进程在被fork后可以通过调用exec函数执行其他程序。
fork函数
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
一般情况下,函数最多有一个返回值,但fork函数非常特殊,它有两个返回值,即调用一次返回两次。成功调用fork函数后,当前进程实际上已经分裂为两个进程,一个是原来的父进程,另一个是刚刚创建的子进程。父子进程在调用fork函数的地方分开,fork函数有两个返回值,一个是父进程调用fork函数后的返回值,该返回值是刚刚创建的子进程的ID;另一个是子进程中fork函数的返回值,该返回值是0。fork函数返回两次的前提是进程创建成功,如果进程创建失败,则只返回-1。两次返回不同的值,子进程返回值为0,而父进程的返回值为新创建的子进程的进程ID,这样可以用返回值来区别父、子进程。
示例程序1
演示fork函数的用法
#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;int main(){pid_t pid;printf("Process creation study\n");pid=fork();//创建子进程switch (pid){case 0://子进程printf("Child process is running,CurPid is %d,ParentPid is %d\n",pid,getppid());break;case -1://创建失败perror("Process creation failed\n");break;default://父进程printf("Parent process is running,ChildPid is %d,ParentPid is %d\n",pid,getpid());break;}exit(0);
}
运行结果
可以看到,fork在父子进程的返回值不同。程序在fork执行完后在此处创建一个子进程,父子进程会分别一起执行fork之后的语句。
结果里是父进程先于子进程执行,但一般来说是不固定的
示例程序2
为了演示这种不固定,可以看以下示例程序
#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;int main(){pid_t pid;const char *msg;int k;printf("Process creation study\n");pid=fork();switch (pid){case 0:msg="Child process is running\n";//子进程会输出的字符串k=3;break;case -1:perror("Process creation failed\n");break;default:msg="Parent process is running\n";//父进程会输出的字符串k=5;break;}while (k>0){fputs(msg,stdout);sleep(1);k--;//子进程和父进程的k不相同}exit(0);
}
运行结果:
可以看出父子进程交替执行
sleep()
函数是一个标准C库函数,用于让程序暂停执行指定的时间,以秒为单位。在Linux/Unix系统中,它的声明在头文件<unistd.h>
中。
前面提到 fork在创建进程失败时,返回-1。失败的原因通常是父进程拥有的子进程的个数超过了规定的限制,此时errno值为EAGAIN。如果可供使用的内存不足也会导致进程创建失败,此时errno值为 ENOMEM
子进程会继承父进程的很多属性,主要包括用户ID、组ID、当前工作目录、根目录、打开的文件、创建文件时使用的屏蔽字、信号屏蔽字、上下文环境、共享的存储段、资源限制等。子进程与父进程有一些不同的属性,主要有如下这些。
- 子进程有它自己惟一的进程ID。
- fork的返回值不同,父进程返回子进程的ID,子进程的则为0。
- 不同的父进程ID。子进程的受进程D为创建它的父进程ID。
- 子进程共享父进程打开的文件描述符,但父进程对文件描述符的改变不会影响子进程中的文件描述符。
- 子进程不继承父进程设置的文件锁。
- 子进程不继承父进程设置的警告。
- 子进程的未决信号集被清空。
示例程序3
本示例演示孤儿进程
如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由 init进程收养,成为init进程的子进程。
#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
int main(){pid_t pid;pid=fork();switch (pid){case 0://子进程进入一个死循环,一直不会结束while (1){printf("A background process,PID:%d\n,ParentID:%d\n",getpid(),getppid());sleep(3);}case -1:perror("Process creation failed\n");exit(-1);default://父进程先结束printf("I am parent process,my pid is %d\n",getpid());exit(0);} return 0;
}
这我就不运行了,子进程根本结束不了,预期的运行结果是这样的
父进程打印一次就执行完毕了,此后子进程就成了孤儿进程,由init进程收养,可以看到此时子进程的父进程ID变为1.
vfork函数
vfork也可以用来创建新进程,和fork相比,他有独特用处。
返回值:
fork()
的返回值是新创建的子进程的进程ID(PID)在父进程中,而在子进程中返回0。因此,通过返回值的不同可以在父进程和子进程中采取不同的操作。vfork()
的返回值也是新创建的子进程的PID,但与fork()
不同的是,父进程和子进程共享地址空间,所以在子进程中对地址空间的修改会影响到父进程。地址空间:
- 在
fork()
中,子进程获得父进程的完整地址空间的副本,父子进程互不干扰,各自独立执行。- 在
vfork()
中,子进程共享父进程的地址空间,子进程运行在父进程的地址空间中,直到调用exec
函数或者exit()
。执行时机:
- 在
fork()
中,父进程和子进程之间的执行是并发的,即它们可以同时运行。- 在
vfork()
中,父进程会被挂起,直到子进程调用exec
函数或者exit()
,因为子进程共享父进程的地址空间,如果父进程继续执行可能会影响到子进程的执行。用途:
fork()
适用于创建一个独立的进程,父子进程各自执行,相互不影响。通常用于多任务并发执行。vfork()
主要设计用于在子进程中立即执行一个新程序,它更加轻量级,因为不需要复制整个地址空间,但是需要小心使用,以避免父子进程之间的竞态条件。总体而言,
fork()
适用于大多数常规情况,而vfork()
更适用于特殊情况,例如在子进程中立即执行一个新程序,而不涉及大量的地址空间复制。
简单来说,vfork创建的子进程会先执行,执行完毕父进程才能继续,而且子进程对变量的修改父进程可见且受影响,因为它们共享地址空间。
使用vfork需谨慎。
示例程序4
下面例子说明两者的不同
#include<cstdlib>
#include<cstring>
#include <cstdio>
#include<ctime>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<cerrno>
using namespace std;
int globVar=5;//全局变量int main(){pid_t pid;int var=1,i;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);break;case -1:perror("Process creation failed\n");exit(-1);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);break;} exit(0);
}
首先我们运行fork版本的
可以看到子进程和父进程不管是全局的globvar还是局部的var都增加了对应的值,证明子父进程地址空间是独立的。而且子父进程的执行顺序不确定。
下面我们把fork注释,把vfork取消注释,再次运行
可以看到父进程结束时的var的值均是在子进程结束时的基础上加的5,证明它们共享地址空间。而且子进程执行完毕后父进程才能继续执行。
书内还有守护进程的内容,不过感觉 跨度有点大,就先跳过了