主要内容
①进程创建
②执行程序
③进程终止
④进程属性
1:进程标识符
1.1
①每个进程都有一份非负整数表示的唯一进程ID
②进程ID可以重新,一个进程结束之后可以,这个ID可以被其他进程所使用,当UNIX普遍都采用了延迟重用算法。使得某一个进程结束之后其ID不会马上被新的进程所使用,以防止将新进程误认为之前结束的进程.
③进程ID为0的通常是调度进程,常称为交换进程(swapper),属于内核的一部分,不执行磁盘上的任何程序,因而也称为系统进程。
④进程ID为1的通常是init进程,负责自举内核之后,启动一个UNIX系统。注意它是一个普通用户进程,但是以超级用户的权限运行。
⑤ID为2的是页守护进程,负责支持虚拟存储系统的分页操作
1.2获取进程标识的函数
#include<unistd.h>
pid_t getpid(); //获取进程ID
pid_t getppid(); //获取父进程ID
pid_t getuid(); //获取实际用户ID,运行这个进程的用户是谁
pid_t geteuid(); //获取有效用户ID,用于文件访问权限检查,决定了对于某个文件的访问权限
pid_t getgid();
pid_t getegid();
2:fork函数
2.1
①函数原型
#include<unistd.h>
pid_t fork(void);
②创建一个新的进程
③调用一次,返回两次子进程中返回0,父进程当中返回子进程的ID
④子进程是父进程的副本,获得了父进程的进程空间数据,包括数据段,bss段,堆,栈。而正文段则是父子进程共享。现在很多系统的实现并不采用完全复制的方法,而是采用写时复制技术。父子进程共享,内核将此区域设置为只读,父子进程任何一个试图修改,内核都将为修改区域的内存制作一个副本。
2.2文件共享
①父子进程共享文件表项
②通常对父子进程对文件描述符的处理方法
a:父进程等待子进程操作文件
b:父子进程使用不同的代码段,即在fork之后,父子进程各自关闭不需要的文件描述符。
③父子进程文件共享图示
3.3fork的两种常见用法
①父进程希望复制自己,父子进程执行不同代码.例如网络服务器,父进程等待请求,当请求到来之后,子进程处理请求,而父进程继续等待下次请求。
②一个进程需要执行一个不同的程序, 子进程fork返回之后立刻调用exec
3:vfork函数
3.1 vfork和fork的区别
①vfork的目的就是执行一个新的程序.
②vfork并不复制父进程的地址空间,相反它在调用exit或者exec之前它在父进程的地址空间中运行.
③vfork保证子进程先执行,只有当子进程调用exec或者exit之后,父进程才有可能被调度运行。
4:进程的结束
4.1
无论是正常退出还是异常终止的进程,其父进程都可以获得其终止状态.正常退出的进程,通过调用exit _exit 或者_Exit将其退出状态作为参数传递给函数而对于异常终止的进程,内核产生一个指示异常终止原因的终止状态
4.2
父进程已经结束的进程都将会被init进程收养
4.3僵死进程
一个 已经终止,但父进程没有对其进行善后处理的进程称为僵死进程.
一个进程结束时,如果父进程仍然存在,而且父进程在fork之前既没有安装SIGCHID信号处理函数调用wait/waitpid等待子进程结束,也没有显示的忽略该信号,则这个子进程在父进程结束之前都为僵死进程,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位 置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间.
5:wait和waitpid
5.1函数原型
#include<sys/wait.h>
pid_t wait(int *statloc)
pid_t wait(pid_t pid, int *statloc, int options)
5.2
一个进程正常或者异常终止,内核都会向其父进程发送一个SIGCHLD信号,由于子进程的终止可能发生在任何时候,所以这也是个异步信号。对于这个信号父进程可以选择忽略,或者提供一个信号处理函数。如果什么不做,系统是默认忽略,如果采用默认忽略的方式那么子进程就会成为一个僵死进程(父进程为init除外)
。如果父进程调用了wait或者waitpid则会发送如下情况。
a:如果所有子进程均在运行,则阻塞
b:如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回。
c:如果没有任何子进程则立即返回错误
example:
#include<stdio.h>
#include<sys/wait.h>
int main()
{
int stats;
pid_t pid;
if( (pid = fork()) < 0){
printf("fork error\n");
return 1;
}
else if(pid == 0){
int i;
printf("this is the child process\n");
for(i=0; i<10; ++i){
sleep(1);
printf("ok!\n");
}
}
else{
if(wait(&stats) != pid){
printf("wait error\n");
}
printf("stats:%d\n",stats);
return 0;
}
}运行结果如下
5.3 wait和waitpid的区别
wait在第一个子进程终止之前会使父进程一直处于阻塞状态而waitpid不会,waitpid有若干个选项可以控制所等待的进程。
注意调用一次之会等待第一个进程的结束,
5.4
wait.h中定义了四个互斥的宏来获取终止原因。p180
5.5fork两次,一种避免产生僵死进程的方法
原理:进程A fork出进程B,之后进程B发 fork出进程C,进程B立即退出,进程C调用sleep函数保证进程B先退出,进程A中调用waitpid函数等待进程B退出。
我们实际需要的新的进程是C,进程B做个中间进程,因为进程B结束之后,进程C成为init的子进程,所以它不会成为僵死进程,而进程A在进程B结束之后它就不会再阻塞。
6:exec函数
6.1:总共有六个exec函数,区别如下
p:使用环境变量中的PATH
l:使用list
v:使用数组
e:使用自定义的环境变量
6.2:大部分unix实现当中只有一个execve是系统调用,其他六个均为库函数