Unix高级编程 进程控制(1)

 看到这里首先需要对进程有一个清晰的了解,才能继续,过些日子,我在写一篇有关进程的基础理解的文章。 这里认为大家对进程已经有了一个初步的认知。这些文章都是对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)最后一个线程对取消请求做出响应。

    不管进程如何终止,最后都会执行内核中的同一段代码,这段代码为相应的进程关闭所有打开的应用,释放它所使用的储存器。 进程将退出状态作为参数传递给终止函数。

上一篇:代码调试-入门、实践到原理


下一篇:胡晓明:“自主研发、共生共存、不碰数据是我们的三条生命线”