Linux系统程序设计——进程

1 进程启动和终止

  • c 程序启动过程
    • 内核启动特色例程
    • 启动例程
      • 在进程的main函数执行之前内核会启动
      • 该例程放置在/lib/libc.so.***中
      • 编译器在编译时会将启动例程编译进可执行文件中
    • 启动例程作用
      • 搜集命令行的参数传递给main函数中的argc和argv
      • 搜集环境信息构建环境表并传给main函数
      • 登记进程的终止函数
  • 进程终止
    • 正常终止
      • 从main函数返回
      • 调用exit(标准c库函数)
      • 调用_exit或_Exit(系统调用)
      • 最后一个线程从其启动例程返回
      • 最后一个线程调用pthread_exit
    • 异常终止
      • 调用abort
      • 接受到一个信号并终止
      • 最后一个线程对取消请求做处理响应
    • 进程返回
      • 通常程序运行成功返回0,否则返回非0
      • 在shell中可以查看进程返回值(echo $?)
  • 进程启动和终止过程 在这里插入图片描述
  • 终止函数登记
    在这里插入图片描述

2 进程状态、进程创建和进程分类

2.1 进程查看和进程状态

  • 查看当前进程
    ps
    
  • 查看所有进程
    ps -ef
    
  • 查看进程状态
    ps -aux
    
    在这里插入图片描述
    在这里插入图片描述
  • 进程状态stat
    在这里插入图片描述

2.2 进程调度和进程状态变化

  • 进程调度
    在这里插入图片描述
  • 进程状态变化关系
    在这里插入图片描述

2.3 进程标识

在这里插入图片描述

2.4 创建进程

在这里插入图片描述

/*******************************************************************
 * Description: fork建立子进程例程
 * 在父进程中fork返回的是子进程的pid
 * 子进程(在子进程中返回的是0
 * 父子进程不一谁先执行,根据系统调度算法决定
 *******************************************************************/
int example2()
{
    printf("pid: %d\n", getpid());

    pid_t pid;
    pid = fork(); //创建子进程

    //在fork之后会运行两个进程(父进程和子进程)
    if (pid < 0) {
        perror("fork error\n");
    } else if (pid > 0) { //父进程(在父进程中fork返回的是子进程的pid)
        printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
    } else { //子进程(在子进程中返回的是0)
        printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
    }
    printf("pid=%d\n", getpid());
    sleep(1);
    return 0;
}

在这里插入图片描述

/*******************************************************************
 * Description: 父子进程交替运行
 *******************************************************************/
int example3()
{
    printf("current pid: %d\n", getpid());

    pid_t pid;
    pid = fork(); //创建子进程

    //在fork之后会运行两个进程(父进程和子进程)
    if (pid < 0) {
        perror("fork error\n");
    } else if (pid > 0) { //父进程
        for (int i = 0; i < 10; i++) {
            printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
            sleep(1);
        }
    } else { //子进程
        for (int i = 0; i < 10; i++) {
            printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);
            sleep(1);
        }
    }

    return 0;
}
  • 父进程fork子进程,子进程会继承父进程的一些信息。数据段、堆、栈是复制过去的,物理内存各自有各自的空间。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
/*******************************************************************
 * Description: 子进程的内存、堆、栈与父进程的关系
 *******************************************************************/
int g_v = 30; //全局变量存放在数据段
int example4()
{
    int a_v = 30; //局部变量存放在栈中
    static int s_v = 30; //静态变量存放在数据段
    printf("current pid: %d\n", getpid());

    pid_t pid;
    pid = fork(); //创建子进程

    //在fork之后会运行两个进程(父进程和子进程)
    if (pid < 0) {
        perror("fork error\n");
    } else if (pid > 0) { //父进程

        g_v = 40;
        a_v = 40;
        s_v = 40;
        printf("I am parent process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);

        printf("g_v: %p, a_v: %p, s_v: %p\n", &g_v, &a_v, &s_v);

    } else { //子进程
        g_v = 50;
        a_v = 50;
        s_v = 50;
        printf("I am child process,pid is %d,ppid is %d, fork return is %d\n", getpid(), getppid(), pid);

        printf("g_v: %p, a_v: %p, s_v: %p\n", &g_v, &a_v, &s_v);
    }
    printf("pid: %d\n", getpid());

    return 0;
}

在这里插入图片描述
可以观察到,父子进程的数据段、堆、栈中的内容被修改,而内存地址是相同的,可以知道fork出的子进程会复制父进程的虚拟内存,但不会复制物理内存。如下图,可直观描述父子进程的内存关系。
在这里插入图片描述

/****************************************************************
 * Description: 父进程打开文件,设置偏移量,子进程追加内容
 * ***************************************************************/
int example5(int argc, char* argv[])
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s file [exit|_exit|return]\n", argv[0]);
        exit(1);
    }
    int fd = open(argv[1], O_WRONLY);
    if (fd < 2) {
        perror("open error!\n");
        exit(1);
    }
    pid_t pid;
    pid = fork(); //创建子进程

    if (pid < 0) {
        perror("fork error\n");
    } else if (pid > 0) { //父进程
        //父进程将文件偏移量调整到文件尾部
        if (lseek(fd, 0L, SEEK_END) < 0) {
            perror("lseek error!");
            exit(1);
        }
    } else { //子进程
        //子进程从文件尾部追加内容
        char* str = "hello world\n";
        ssize_t size = strlen(str) * sizeof(char);
        sleep(3); //等待父进程设置偏移量完成

        //此处的fd是从父进程中复制过来的
        //但是和父进程中的fd都是指向同一个文件
        if (write(fd, str, size) != size) {
            perror("write error!\n");
            exit(1);
        }
    }
    printf("pid: %d finish \n", getpid());
    //父子进程都要关闭各自的文件描述符
    close(fd);
    return 0;
}

  • 如果连续fork() n次,会产生2的n次方个子进程
    在这里插入图片描述

2.6 进程链和进程扇

在这里插入图片描述

  • 创建进程链
/****************************************************************
 * Description: 进程链例程
 * ***************************************************************/
int example6(int argc, char* argv[])
{
    int counter = 0;
    if (argc < 2) { //没有指定创建多少个进程,默认为2
        counter = 2;
    } else {
        counter = atoi(argv[1]); //字符转换成整形
    }
    pid_t pid;
    for (int i = 1; i < counter; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork error!\n");
        } else if (pid > 0) {
            //父进程退出循环,由子进程创建子进程
            break;
        }
    }
    printf("pid: %d ,ppid: %d\n", getpid(), getppid());
    while (1) {
        sleep(1);
    }
    return 0;
}
  • 创建进程扇
/****************************************************************
 * Description: 进程链例扇
 * ***************************************************************/
int example7(int argc, char* argv[])
{
    int counter = 0;
    if (argc < 2) { //没有指定创建多少个进程,默认为2
        counter = 2;
    } else {
        counter = atoi(argv[1]); //字符转换成整形
    }
    pid_t pid;
    for (int i = 1; i < counter; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork error!\n");
        } else if (pid == 0) {
            //子进程退出循环,由父进程创建子进程
            break;
        }
    }
    printf("pid: %d ,ppid: %d\n", getpid(), getppid());
    while (1) {
        sleep(1);
    }
    return 0;
}
ps -ef | grep 进程名   #查看特定的进程
pstree     #进程树,查看父子进程关系

2.7 守护进程、孤儿进程、僵尸进程

  • 守护进程
    在这里插入图片描述
  • 孤儿进程:
    父进程结束,子进程就变成了孤儿进程,会由1号进程(init进程)领养。
  • 僵尸进程:
    子进程退出了,但是父进程没有用wait或waitpid去获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程。
    在这里插入图片描述

3 进程的相关系统调用

3.1 wait函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void out_status(const int status)
{
    if (WIFEXITED(status)) {
        printf("normal exit: %d\n", WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("abnormal exit: %d\n", WTERMSIG(status));
    } else if (WIFSTOPPED(status)) {
        printf("stop exit: %d\n", WSTOPSIG(status));
    } else {
        printf("unknow exit\n");
    }
}
/******************************************************************************************************************
 * Description: wait()回收线程资源,通过宏判断进程终止类型
 * WIFEXITED(status)、WIFSIGNALED(status)、WIFSTOPPED(status)判断是否发生相应类型的终止
    返回值为true/false
 * WEXITSTATUS(status)、WTERMSIG(status)、WSTOPSIG(status)能根据status解析处具体的终止信息
    如果是正常终止,则返回exit传入的数字
 * ****************************************************************************************************************/
int example8()
{
    int status;
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork error\n");
        exit(1);
    } else if (pid == 0) {
        printf("pid: %d, ppid: %d\n", getpid(), getppid());
        exit(3); //子进程终止运行(正常终止)
    }
    //父进程阻塞,等待子进程结束并回收
    wait(&status);
    out_status(status);
    printf("-------------------------------------------------------------------------\n");

    if ((pid = fork()) < 0) {
        perror("fork error\n");
        exit(1);
    } else if (pid == 0) {
        printf("pid: %d, ppid: %d\n", getpid(), getppid());
        int i = 3, j = 0;
        int k = i / j; //除零运算,非正常终止
    }
    //父进程阻塞,等待子进程结束并回收
    wait(&status);
    out_status(status);
    printf("-------------------------------------------------------------------------\n");

    if ((pid = fork()) < 0) {
        perror("fork error\n");
        exit(1);
    } else if (pid == 0) {
        printf("pid: %d, ppid: %d\n", getpid(), getppid());
        pause(); //暂停终止
    }
    //此处为父进程执行,pid为子进程ID号
    do {
        pid = waitpid(pid, &status, WNOHANG | WUNTRACED);
        if (pid == 0) //pid==0证明子进程尚未结束
            sleep(1);
    } while (pid == 0);

    out_status(status);
    printf("-------------------------------------------------------------------------\n");
    return 0;
}

在这里插入图片描述

3.2 exec函数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

/****************************************************************
 * Description: exec函数例程
 * ***************************************************************/
int example9()
{

    //const char* const 定义常量字符串
    const char* const cmd1 = "cat"; //相对路径
    const char* const cmd2 = "/bin/cat"; //绝对路径
    const char* const argv1 = "/etc/passwd";
    const char* const argv2 = "/etc/group";
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("fork error\n");
        exit(1);
    } else if (pid == 0) {
        //子进程调用exec函数,执行新的程序
        if (execl(cmd2, cmd1, argv1, argv2, nullptr) < 0) {
            perror("execl error!\n");
            exit(1);
        }
        printf("after execl...\n"); //如果execl调用成功,此句不会执行
    }
    sleep(1);
    printf("----------------------------------------------------------------------\n");
    char* argv[4] = { (char*)cmd1, (char*)argv1, (char*)argv2, NULL };
    if ((pid = fork()) < 0) {
        perror("fork error\n");
        exit(1);
    } else if (pid == 0) {
        //子进程调用exec函数,执行新的程序
        if (execvp(cmd2, argv) < 0) {
            perror("execl error!\n");
            exit(1);
        }
        printf("after execl...\n"); //如果execl调用成功,此句不会执行
    }
    sleep(1);
    printf("----------------------------------------------------------------------\n");
    wait(nullptr);
    return 0;
}

3.3 system函数

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值