进程控制

    进程号

         每个进程都由一个进程号来标识, 其类型为 pid_t, 进程号的范围: 0~32767( 2^15 )

         进程号总是唯一的, 但进程号可以重用

         当一个进程终止后, 其进程号就可以再次使用了

          在 linux 系统中进程号由 0 开始

               进程号为 0 及 1 的进程由内核创建

                      进程号为 0 的进程通常是调度进程, 常被称为交换进程(swapper)

                      进程号为 1 的进程通常是 init 进程

            除调度进程外, 在 linux 下面所有的进程都由 进程 init 进程直接或者间接创建

        进程号(PID):

              标识进程的一个非负整型数

         父进程号(PPID):

              任何进程(除 init 进程)都是由另一个进程创建, 该进程称为 被创建进程的父进程, 对应的进程号称为父进
程号(PPID)

        进程组号(PGID):

              进程组是一个或多个进程的集合

             他们之间相互关联, 进程组可以接收同一终端的各种信号,关联的进程有一个进程组号(PGID)

        Linux 操作系统提供了三个获得进程号的函数 getpid()、 getppid()、 getpgid()

例子:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    pid_t ppid;
    pid_t pgid;

    // 获取本进程号
    pid = getpid();
    // 获取调用此函数的进程的父进程号
    ppid = getppid();
    // 获取进程组号,参数为 0 时返回当前 PGID, 否则返回参数指定的进程的 PGID
    pgid = getpgid(pid);

    printf("pid = %d\n",pid);
    printf("ppid = %d\n",ppid);
    printf("pgid = %d\n",pgid);
    return 0;
}

 结果:

    进程的创建 fork 函数

#include <sys/types.h>
#include <unistd.h>

/*
 * 从一个已存在的进程中创建一个新进程, 新进程称为子进程, 原进程称为父进程
 *return:
 *     成功:子进程返回0 父进程返回子进程ID
 *     错误:-1
 */
pid_t fork(void);
pid_t vfork(void);

        地址空间:
             进程上下文、 进程堆栈、 打开的文件描述符、 信号控制设定、 进程优先级、 进程组号
            子进程所独有的只有它的进程号, 计时器
 

 创建一个子进程实现多任务:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
    }

    if(0 == pid)
    {
        while(1)
        {
            printf("this is son process\n");
            sleep(1);
        }
    }
    else
    {
        while(1)
        {
            printf("this is father process\n");
            sleep(1);
        }
    }
    return 0;
}

结果: 

验证父子进程分别有各自独立的地址空间

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int var = 10;

int main(int argc, char *argv[])
{
    pid_t pid;
    int num = 9;

    pid = fork();
    if(pid < 0)
    {
        perror("fork");
    }

    if(0 == pid)
    {
        var++;
        num++;
        printf("in son process var = %d, num = %d\n",var, num);
    }
    else
    {
        sleep(1);
        printf("in father process var= %d, num = %d\n",var, num);
    }
    printf("common code area\n");
    return 0;
}

结果:

结论:

      子进程对变量改变并不影响 父进程中该变量的值, 说明父子进程各自拥有自己的地址空间

    在 fork 之后是父进程先执行还是子进程先执行是不确定,这取决于内核所使用的调度算法
    如 要求父子进程之间相互同步, 则要求某种形式的进程间通信

 

标准 I/O 提供三种类型的缓冲:
     全缓冲:(大小不定)
         填满标准 I/O 缓冲区后, 才进行实际的 I/O 操作

        术语 冲洗缓冲区的意思:进行标准 I/O 写操作
    行缓冲: (大小不定)
          在遇到换行符时, 标准 I/O 库执行 I/O 操作

          这种情况允许我们一次输入一个字符, 但只有写了一行后才进行实际的 I/O 操作
     不带缓冲

验证子进程继承父进程的缓冲区:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
    pid_t pid;
    int length = 0;
    char buf[] = "a write to stdout\n";

    length = write(1, buf, strlen(buf));

    if(length != strlen(buf))
    {
        printf("write error\n");
    }
    
    printf("before fork\n");
    pid = fork();
    if(pid < 0)
    {
        perror("fork");
    }

    if(0 == pid)
    {
        printf("in son process\n");
    }
    else
    {
        sleep(1);
        printf("in father process\n");
    }

    return 0;
}

打印:

提示:
     调用 fork 函数后, 父进程打开的文件描述符都被复制到子进程中

   在重定向父进程的标准输出时, 子进程的标准输出也被重定向
       write 函数是系统调用, 不带缓冲
     标准 I/O 库是带缓冲的, 当以交互方式运行程序时, 标准 I/O 库是是行缓冲的, 否则它是全缓冲的