进程控制

fork函数 创建一个子进程。

pid_t fork(void);

失败返回-1;成功返回:
  1. 父进程返回子进程的ID(非负)
  2. 子进程返回 0
  3. pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
    注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
循环创建N个子进程 从上图我们可以很清晰的看到,当n为3时候,循环创建了(2^n)-1个子进程,而不是N的子进程。需要在循环的过程,保证子进程不再执行fork ,因此当(fork() == 0)时,子进程应该立即break; 才正确。
练习:通过命令行参数指定创建进程的个数,每个进程休眠1S打印自己是第几个被创建的进程。如:第1个子进程休眠0秒打印:“我是第1个子进程”;第2个进程休眠1秒打印:“我是第2个子进程”;第3个进程休眠2秒打印:“我是第3个子进程”。
//fork1.c #include #include #include int main(void) { int i; pid_t pid; printf("xxxxxxxxxxx\n"); for (i = 0; i < 5; i++) { pid = fork(); if (pid == 0) { break; } }if (i < 5) {sleep(i); printf("I'am %d child , pid = %u\n", i+1, getpid()); } else{ sleep(i); printf("I'm parent\n"); }return 0; }

getpid函数 获取当前进程ID
pid_t getpid(void);

getppid函数 获取当前进程的父进程ID
pid_t getppid(void);

区分一个函数是“系统函数”还是“库函数”依据:
  1. 是否访问内核数据结构
  2. 是否访问外部硬件资源
    二者有任一 → 系统函数;二者均无 → 库函数
getuid函数(了解) 获取当前进程实际用户ID
uid_t getuid(void);

获取当前进程有效用户ID
uid_t geteuid(void);

getgid函数(了解) 获取当前进程使用用户组ID
gid_t getgid(void);

获取当前进程有效用户组ID
gid_t getegid(void);

进程共享 父子进程之间在fork后。有哪些相同,那些相异之处。 刚fork之后: 父子相同处:
全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处:
  1. 进程ID
  2. fork返回值
  3. 父进程ID
  4. 进程运行时间
  5. 闹钟(定时器)
  6. 未决信号集
似乎,子进程复制了父进程0-3G用户空间内容,以及父进程的PCB,但pid不同。真的每fork一个子进程都要将父进程的0-3G地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
实例:编写程序测试,父子进程是否共享全局变。
//fork_shared.c #include #include #include int a = 100; //.data int main(void) { pid_t pid; pid = fork(); if(pid == 0){//son a = 2000; printf("child, a = %d\n", a); } else { sleep(1); //保证son先运行 printf("parent, a = %d\n", a); }return 0; }

重点注意!躲避父子进程共享全局变量的知识误区!
【重点】:父子进程共享:
1. 文件描述符(打开文件的结构体)
2. mmap建立的映射区(进程间通信详解)
【进程控制】特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算法。

    推荐阅读