看到这里首先需要对进程有一个清晰的了解,才能继续,过些日子,我在写一篇有关进程的基础理解的文章。 这里认为大家对进程已经有了一个初步的认知。这些文章都是对unix高级编程这本书进行的总结,一方面是方便自己回顾,一方面是加深自己的理解,也可以向大家分享一下学习的经验。
进程的控制,主要包含创建进程、执行程序、进程终止。
进程标识
进程标志也可以说是进程的号,专业点叫进程ID。进程的标识符ID 是唯一的,也就是每一个进程及对应的ID 都具有唯一性。 进程ID 虽然具有唯一性,但进程ID 具备可重复性,当一个进程终止后,其进程ID 就是可以被复用的ID。
进程中有一些专用的进程,但具体细节随着实现的不同。
ID:0 系统的调度进程,也称交换进程。(swapper进程)。
ID: 1 init进程, 在自举过程结束时由内核调用。(自举是什么意思)
ID: 2 页守护进程(page daemon)。此进程负责支持虚拟储存系统的分页操作。
#include <unistd.h>
pid_t getpid(void); //调用进程的ID
pid_t getppid(void); // 调用进程的父进程ID
uid_t getuid(void); //调用进程的实际用户ID
uid_t geteuid(void); //调用进程的有效用户ID
gid_t getgid(void); // 调用进程的实际组ID
gid_t getegid(void); //调用进程的有效组ID
创建一个进程:
#include <unistd.h>
pid_t fork(void);
由fork创建的进程被成为子进程,fork函数被调用一次,被返回两次。
其中:
父进程返回的子进程的进程ID。
子进程的返回的是0。
子进程和父进程继续执行fork调用之后的指令。 子进程是父进程的副本。 例如: 子进程获得父进程的数据空间、堆和栈的副本, 这是子进程所拥有的副本,父进程和子进程并不共享这些储存空间,父进程和子进程共享正文段。
由于fork 之后,经常跟随着exec,所以出现了一个新的内容,写时复制。 意思就是,将这一段区域由子进程和父进程共享,内核将它们的访问权限设置为只读,只有当子进程或者父进程想要修改的时候,则内存只为修改那部分创建一份副本。通常为虚拟系统中的一个页。
另外还有一个问题,是fork 之后是父进程先执行,还是子进程先执行,是不确定的。 这与操作系统的内核有关系,如果想让某个进程先执行,我们需要以某种形式,进行进程之间的通信。
#include "apu.h"
int val = 6;
char buf[] = "a write to stdout";
int main(void)
{
int var;
pid_t pid;
tempvalue = 88;
if(pid = fork() <0){
printf("fork error");
} else if (pid == 0){
val++;
tempvalue++;
}else{
sleep(2);
}
printf("pid = %ld,val = %d, var = %d\n",(long)getpid(),val,tempvalue);
}
//
结果: 子进程: var = 7; tempvalue = 89;
父进程: var =6.tempvalude =88
原因就是,子进程和父进程共享一份代码,但子进程拥有父进程fork 之前的数据副本。
文件共享
子进程和父进程之间的 文件共享
fork 具有另外一个特点,父进程所有打开的文件描述符,全部被复制到子进程。 这里说的复制是只文件描述符类似全部 执行乐dup 函数。(这里去看文件描述符部分)
dup (int fd);
// 复制一个现有的文件描述符,返回一个指向文件描述符的新值
//dup 函数返回的一定是当前文件描述符可以使用的最小值
复制的文件描述符,和原来的文件描述符,共同指向同一个文件表项。
有关文件表的内容,我将在linux 操作系统 的文件系统中进行详细解释。
需要强调的一点是,父进程和子进程 共享一个文件偏移量, 也就是fork 之后,子进程和父进程同时往一个文件内输出,容易照成输出混合, 所以父进程和子进程之间要进行同步处理。
fork 之后的文件描述符,有两种常见的状况:
(1) 父进程等待子进程完成。父进程在子进程终止前,不对文件描述符进行处理。
(2) 当进程终止之后,对任一文件描述符进行做相应的更新。父进程和子进程执行不同的代码段,关闭它们不需要使用的文件描述符。 这样就不会干扰对方使用文件描述符。
子进程继承 父进程的属性:
1. 实际用户ID、实际组ID、有效用户ID、有效组ID。
2. 附属组ID。
3. 进程组ID。
4.会话ID。
5. 当前工作目录根目录。
6. 环境、储存影像、资源限制。
父进程和子进程之间的区别是什么呢?
fork 的返回值不同,进程ID 不一样。
两个进程的父进程ID 不一样。
子进程不继承父进程设置的文件锁。
子进程未处理的闹钟被清零。
子进程设置的未处理信号集设置为空集。
fork 之后 对子进程有两中想法,
一是父进程和子进程执行不同的代码段。
二是 一个进程要执行另外的程序,在fork 之后,执行exec()。
函数 vfork
vfork 的返回值和调用系列和fork 相同,但语义不同。
vfork 函数的目的是用于创建一个线程,而且该线程的目的是执行exec一个新的程序。 所以它不会将父进程的地址空间完全复制到子进程中去,因为子进程会立刻调用exec 函数,所以在调用exec函数之前,子进程是在父进程的空间中运行。
vfork 和fork 之间的一个区别是,能够保证子进程先运行,在子进程调用exec或exit后父进程才可能被调度运行。
#include "apue.h"
int globvar = 6
int main(void)
{
int var;
int pid;
printf("before vfork\n");
if((pid==vfork()==0)
{
globavr++;
var++;
_exit(0);
} else if(pid>0){
print("pid = %ld, glob = %d,var = %d\n",(long)getpid(),globvar,var)
}
exit(0);
}
运行结果是 glob = 7,var = 89。
子进程对变量进行增加,改变了父进程中变量的值,因为子进程在父进程中运行。
8、进程退出的方式
进程有5中正常终止和异常终止方式。
(1) 在main 函数中调用return 函数。
(2) 调用exit() 函数。这个函数有ISO C 定义。其会调用各种注册的终止函数。
(3)调用_exit() 或_Exit()函数,不需要调用终止函数。
(4) 进程中的最后一个线程,在其启动例程中执行return 语句。
(5) 进程中的最后一个线程调用pthread_exit函数。
3种异常终止的函数:
(1)调用abort 函数。
(2)当进程接受到某些信号。
(3)最后一个线程对取消请求做出响应。
不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应的进程关闭所有打开的应用,释放它所使用的储存器。 进程将退出状态作为参数传递给终止函数。