unix编程以及xv6系统浅谈(五)进程控制

440 阅读6分钟

5.1 进程标志

​ 每个进程都有一个pid唯一的表示它,虽然是唯一的,但是是可以复用的。

​ ID为0的进程通常是调度进程,由于不执行任何磁盘上的程序,所以又是系统进程。

​ ID为1的进程通常是init进程,在自举过程结束时由内核调用。

​ ID为2的是页守护进程,此进程负责支持虚拟存储器系统的分页操作。

pid_t getpid()
    获取调用进程的pid
pid_t getppid()
    获取调用进程的父进程pid
uid_t getuid()
    获取调用进程的用户id
gid_t getgid()
    获取调用进程的组用户id

5.2 创建进程

​ 可以使用一些函数创建一个进程

​ 子进程和父进程会继续执行fork之后的命令,子进程获得父进程数据空间,堆栈的副本,但是不是共享的,父子进程共享的只有正文段

​ 子进程还会复制父进程中标准io缓冲区中未清楚的部分

​ 注意由于文件部分的实现,文件描述符指向的文件表项是由内核管理,所以父子进程是会指向同样的文件表项的,除非修改文件描述符,文件表项中包含了文件的偏移量(即任意进程修改过后下一个进程读到的就是修改的内容)!

​ 网络服务进程中一般会各自关闭它们不需要的文件描述符,以免产生不必要的影响。

​ 现在的很多实现并不执行一个父进程数据段,堆栈的完全副本,使用了写时复制的技术。

​ 这些区域由父子进程共享,内核将它们的访问权限改变为只读。如果父进程或者子进程中的任意一个试图修改这些区域,则内核只为修改区域那一块内存制作一块副本,通常是虚拟内存中的一页。

​ 事实上,内核也为子进程分配了一段页表,但是这些页表指向父进程,并且标记为只读,当子进程试图写的时候会发生写保护的错误,错误处理方式就是复制出错的内容进入页表。

pid_t fork()
    两种不同的返回值
    	子进程返回0
    	父进程返回创建的子进程的pid

vfork不同关于fork,一般它是为了调用exec才创建的,所以

​ vfork中:

​ 在父进程的空间中运行,不会像fork一样执行写时复制!其实就是关闭了页表的只读变成可写。

​ 一旦子进程修改数据(除了vfork的返回值),进行函数调用,或者没有exec或者exit就返回都可能带来问题

​ 保证子进程先运行,一旦子进程中调用了exec或者exit之后才会调度父进程恢复运行。

​ 注意exit()函数和_exit()函数的区别,exit()函数会冲洗缓冲区,所以父进程的缓冲区很可能会被清洗。

pid_t vfork()

5.3 僵尸进程和孤儿进程

孤儿进程

​ 对于父进程已经终结的进程,它们的父进程都会改变为init进程

​ 操作过程为: 当一个进程终结时,内核会逐个检查所有活动进程,以判断它是否是正要终结的进程的子进程,如果是,则将其的父进程id 改为1

​ 注意,孤儿进程并不会变为僵尸进程,因为它终结的时候init进程必定会调用一个wait函数取得其终止状态。

僵尸进程

​ 终止进程的父进程调用wait或者waitpid时,可以得到一些子进程的信息,包括进程id,终止状态和CPU时间总量。内核会根据这些信息释放终止进程的存储区,关闭其所有打开的文件。

​ 一个已经终止,但是父进程尚未对其进行善后处理的进程称为僵尸进程。

//老子不收尸,儿子变僵尸
//孤儿不用怕,养父是老大
//生个孙子当龙子,哈哈哈
//简单的例子,不需要等待孙子进程,孙子进程也不会变成僵尸进程
if(fork())
{
 //进程1处理进程2结束
  wait(NULL);
}
else
{
    //进程2fork出进程3再终结
    if(fork())
    {
        exit();
    }
    else
    {
        //成功获得养父进程init
    }
    
}

5.4 进程结束处理

​ 当一个进程正常或异常终止的时候,内核就会向其父进程发送SIGCHLD信号,这是个异步事件,可能在父进程运行的任何时候发生。

//返回终结的子进程id
pid_t wait(int *statloc)
    如果子进程已经终止并且是个僵尸进程,则立即返回并且取得该子进程的状态
    否则wait阻塞直到任意一个子进程终结,由于返回子进程的id,所以总会知道是哪个子进程终结了
	statloc:
				NULL	不关心终止状态
				整形指针	将终止状态填入地址指向的内存中
pid_t waitpid(pid_t pid,int *statloc,int options)
    pid:
    		-1	等待任意子进程
    		>0	等待进程id与pid相等的子进程终结
    		0	等待组id等于调用进程组id的任一子进程
    		<-1	等待组id等于pid绝对值的任一子进程
    statloc同上
    options:
    		0	阻塞等待
    		或者下面的按或的结果
    			WCONTINUE	
    			WNOHANG		不阻塞直接返回0(如果没有子进程为僵尸进程的话)
    			WUNTRACED

​ 注意,除了父进程可以等待子进程的结束,子进程也可以等待父进程的结束

//子进程等待父进程的结束
while(getppid()!=1)
{
    //非阻塞
}
while(getppid()!=1);	//阻塞方式

5.5 函数exec

​ 当进程调用一种exec的时候,该进程执行的程序完全替换成为新程序,而新程序则从其main函数开始执行,因为调用exec并不创建新进程,前后的进程id未改变,exec只是用磁盘上的一个新进程替换了当前进程的正文段,数据段,堆段和栈段。

​ 用exec可以初始执行新的程序,exit函数和wait函数处理终止和等待终止。

exec
//exec(l,v)(p)
//l表示将参数传入到函数参数中
//v表示将参数定义为一个传入到函数的参数中
//p代表需要自定义环境变量,若没有p则继承进程的环境变量
int system(const char *cmdstring)
    //会调用fork,返回该进程的终止状态
    相当于在命令行中直接输入cmdstring

5.6 更改用户id和更改用户组id

​ 注意需要有超级权限

int setuid(uid_t uid)	//设置用户id
int setgid(gid_t gid)	//设置用户组id