C语言笔记-18-Linux基础-进程

C语言笔记-18-Linux基础-进程



前言

自学笔记,没有历史知识铺垫(省略百度部分)C语言笔记-18-Linux基础-进程


一、进程概括

  1. 程序是计算机指令集合,静态数据
  2. 进程是程序在计算机运行的实例
  3. 程序在运行过程中,要使用计算机资源,就要对进程使用的资源进行记录,所有的记录约等于进程
  4. 进程是资源分配的基本单位,每个进程都有自己的pid和PCB

注意:

  1. 进程有独立的4G地址空间(多个进程轮流布局同一份4G地址空间,同一时刻只有一个进程独立占用)
  2. 进程有自己的pid
  3. 进程有自己的PCB

二、进程指令

  1. ps 查看进程
  2. top 实时查看进程

PCB成员
fd 记录进程对文件资源的使用情况(文件描述符)
image 进程镜像(轮流布局独立4G地址空间映射)

三、进程函数

unistd.h

fork 新建子进程

pid_t fork(void); 新建子进程(新建失败时,不会创建子进程)
返回值: 成功时,在父进程和子进程中各返回一次(常说的一次调用,两次返回)

  1. 成功:pid 子进程的pid 返回到父进程中
  2. 成功:0 返回到子进程中
  3. 失败:-1 返回到父进程中,errno被设置

注意:

  1. 子进程的PCB和父进程的PCB读的部分一致,写的部分,子进程会复制父进程PCB该部分后单独存储(2.6版本后增加,写时拷贝技术),即image布局是一样,但是子进程与父进程各运行在独立的内存空间,二者操作本进程时,不会相互影响

可以通过fork返回标识,控制业务在父进程or子进程中处理(异步)

示例代码

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

int main(int argc, char *argv[])
{
    pid_t pid = fork();
    if (pid == -1)
    {
        printf("父进程执行失败,该信息永远在父进程中输出\n");
    }
    if (pid == 0)
    {
        printf("子进程pid才会返回0,该信息永远是在子进程中输出\n");
    }
    else
    {
        printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
    }
    printf("由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出\n");
    return 0;
}

// 输出结果
父进程才会返回子进程的pid:7724,该信息永远在父进程中输出
由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出
子进程pid才会返回0,该信息永远是在子进程中输出
由于子进程复制父进程的PCB,该信息会在父进程和子进程都会输出

return/exit 关闭子进程

return 标识函数结束,在main函数中,终止进程
exit 终止进程 (stdio操作会被刷新关闭,创建的临时文件也会被移除) 并向父进程返回(status&0377)(0-255)

atexit 遗言函数

进程在正常终止时被调用的函数,如:main函数return后、进程exit后,才会调用的函数

注册遗言函数
atexit() 传入无行参的指针函数

  1. 执行顺序按照注册顺序倒叙执行
  2. 同一个函数可以被注册多次,执行多次
  3. 子进程继承父进程的遗言函数
  4. exec家族中的函数,只要有一个函数调用成功,其他遗言函数会被移除(不执行)
#include <stdio.h>
#include <stdlib.h>

void endCall(){
    printf("遗言函数触发\n");
}

int main(int argc, char *argv[])
{
    atexit(endCall);
    atexit(endCall);
    return 0;
}

// 输出结果
遗言函数触发
遗言函数触发

注册遗言函数
on_exit() 传入有行参的指针函数,并传入返回值,功能与atexit()一样

四、wait 进程资源回收

进程终止的时候,需要将进程资源回收,终止的进程无法自己回收,回收操作由父进程进行。
回收函数wait家族函数回收

wait

阻塞并挂起当前进程,等待任意子进程终止。
wait(int *status)
入参:
*status 将子进程状态信息存储到该指针中
返回值:
成功: pid 返回终止了的子进程的pid
失败: -1 errno被设置

wait的宏

  1. WIFEXITED(status) 子进程正常终止,返回真
  2. WEXITSTATUS(status) 返回子进程的退出状态码(exit(status) status&0377)
    这个宏只能在WIFEXITED返回真时使用
  1. WIFSIGNALED(status) 如果子进程是被信号终止的,返回真
  2. WTERMSIG(status) 返回导致子进程终止的信号的编号
    这个宏只能在WIFSIGNALED返回真时使用

wait可以让异步执行的多进程,转变为同步执行
模拟子进程正常终止示例

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int subExitStatus;
    pid_t pid = fork();
    if (pid == -1)
    {
        printf("父进程执行失败,该信息永远在父进程中输出\n");
    }
    if (pid == 0)
    {
        printf("子进程pid才会返回0,该信息永远是在子进程中输出\n");
        return 99;
    }
    else
    {
        wait(&subExitStatus);

        if (WIFEXITED(subExitStatus))
        {
            printf("子进程正常终止状态码:%d\n", WEXITSTATUS(subExitStatus));
        }

        printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
    }
    printf("由于子进程被提前return,该信息永远在父进程中输出\n");
    return 0;
}

// 执行结果
子进程pid才会返回0,该信息永远是在子进程中输出
子进程正常终止状态码:99
父进程才会返回子进程的pid:12483,该信息永远在父进程中输出
由于子进程被提前return,该信息永远在父进程中输出

模拟子进程信号打断示例

#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int subExitStatus;
    pid_t pid = fork();
    if (pid == -1)
    {
        printf("父进程执行失败,该信息永远在父进程中输出\n");
    }
    if (pid == 0)
    {
        printf("子进程pid才会返回0,该信息永远是在子进程中输出,pid:%d\n", getpid());
        getchar();
        return 99;
    }
    else
    {
        wait(&subExitStatus);

        if (WIFEXITED(subExitStatus))
        {
            printf("子进程正常终止状态码:%d\n", WEXITSTATUS(subExitStatus));
        }
        if (WIFSIGNALED(subExitStatus))
        {
            printf("子进程信号打断状态码:%d\n", WTERMSIG(subExitStatus));
        }

        printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
    }
    printf("由于子进程提前被信号终打断,该信息永远在父进程中输出\n");
    return 0;
}

// 执行结果
子进程pid才会返回0,该信息永远是在子进程中输出,pid:13385
子进程信号打断状态码:8
父进程才会返回子进程的pid:13385,该信息永远在父进程中输出
由于子进程提前被信号终打断,该信息永远在父进程中输出

打断信号

 dony15$ kill -8 13385

waitpid

阻塞并挂起当前进程,等待指定子进程终止。
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid 指定具体的子进程

  1. <-1 等待进程组的pid是|pid|的组中的子进程
  2. -1 等待任意子进程
  3. 0 等待和当前进程同一个进程组的子进程
  4. >0 指定了要等待的子进程的pid

stat_loc 同wait(int *status)的参数
options

  1. WNOHANG 如果没有子进程终止,立即返回
  2. 0 阻塞等待子进程的终止

返回值:
成功 返回状态改变的子进程的pid
如果WNOHANG被指定,没有子进程状态的改变 返回0
错误 -1 errno被设置

// 效果
wait(s)==waitpid(-1,s,0)

五、exec家族函数 进程映像的更新

子进程可以更新image,此时子进程的image与父进程的image将不同(更新子进程image不影响父进程)

execve exec核心函数,所有exec家族函数都是围绕这个函数封装
int execve(const char *path, char *const argv[], char *const envp[]);
功能:执行程序
参数:

  1. filename 要执行的文件名
    这个文件必须是可执行的二进制文件脚本文件
  2. argv 字符串数组 传递给main函数的参数 习惯上第一个是可执行文件的名字
  3. envp name=value 作为环境变量传递给新的可执行程序
    argv和envp以NULL作为结尾
    返回值:
    成功 不返回
    错误 -1 errno被设置

exec家族函数列表

#include <unistd.h>
//指向当前进程的环境变量列表
extern char **environ;  
/* (char  *) NULL */);
int execl(const char *path, const char *arg, ...
                       
int execlp(const char *file, const char *arg, ...
                       /* (char  *) NULL */);
int execle(const char *path, const char *arg, ...
                       /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);

exec 公有的
l l代表list,需要将arg元素以可变参数都传入
v v代表vector,传数组即可
p PATH环境变量 需要知道要加载的可执行程序所在的路径.

  1. 有p字母,会到环境变量PATH指定的路径中找可执行程序
  2. 没有p字母,必须指定可执行文件的所在路径(相对 绝对)

e

  1. 如果有字母e,用户指定环境变量传递给新的可执行程序.
  2. 没有字母e.代表从父进程继承环境变量

execvp示例

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

int main(int argc, char *argv[])
{
    pid_t pid = fork();
    if (pid == -1)
    {
        printf("父进程执行失败,该信息永远在父进程中输出\n");
    }
    if (pid == 0)
    {
        char *ps_argv[] = {"ps", "-o", "pid,ppid,comm", NULL};
        execvp("ps", ps_argv);
        printf("execvp执行失败才会执行\n");
    }
    else
    {
        printf("父进程才会返回子进程的pid:%d,该信息永远在父进程中输出\n", pid);
    }
    printf("由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出\n");
    return 0;
}

// 执行结果
父进程才会返回子进程的pid:14966,该信息永远在父进程中输出
由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出
  PID  PPID COMM
  999   998 -bash
 5836  5805 /bin/bash
10801 10800 -bash
10839 10801 vim
13388  5805 /bin/bash

execlp示例

...
execlp("ps", "ps", "-o", "pid,ppid,comm", NULL);
...
// 执行结果
父进程才会返回子进程的pid:14966,该信息永远在父进程中输出
由于子进程镜像被更新(execvp执行失败,子程序才会执行),与父进程的PCB不同,该信息会在父进程输出
  PID  PPID COMM
  999   998 -bash
 5836  5805 /bin/bash
10801 10800 -bash
10839 10801 vim
13388  5805 /bin/bash

查看当前进程环境变量示例

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

// 当前进程的环境变量列表
extern char **environ;

int main(int argc, char *argv[])
{
    for (size_t i = 0; environ[i] != NULL; i++)
    {
        printf("环境变量:%s\n", environ[i]);
    }
    return 0;
}

// 执行结果
环境变量:SHELL=/bin/bash
环境变量:TMPDIR=/var/folders/3j/nltc60p50b9dl803pp8qkbqc0000gn/T/
环境变量:OLDPWD=/Users/dony15/Desktop/c_code/c_learn_3
环境变量:ORIGINAL_XDG_CURRENT_DESKTOP=undefined
环境变量:MallocNanoZone=0
环境变量:JAVA_11_HOME=/Library/Java/JavaVirtualMachines/jdk-11.0.4.jdk/Contents/Home
环境变量:LC_ALL=en_US.UTF-8
环境变量:NO_PROXY=127.0.0.1
环境变量:COCOS_CONSOLE_ROOT=/Users/dony15/Desktop/cocos2d-x-4.0/tools/cocos2d-console/bin
环境变量:USER=dony15
环境变量:COCOS_TEMPLATES_ROOT=/Users/dony15/Desktop/cocos2d-x-4.0/templates
环境变量:COMMAND_MODE=unix2003
环境变量:JAVA_12_HOME=/Library/Java/JavaVirtualMachines/jdk-12.0.2.jdk/Contents/Home
环境变量:SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.cT3LzZpnc1/Listeners
环境变量:__CF_USER_TEXT_ENCODING=0x1F5:0x19:0x34
...

execle示例
main.c

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

int main(int argc, char *argv[])
{
    char *m_env[] = {"name=abc", "age=15",NULL};
    int a = execle("./t_child.out", "abc", NULL, m_env);
    perror("err:");
    return 0;
}

t_child.c

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

// 当前进程的环境变量列表
extern char **environ;
int main(int argc, char *argv[])
{
    printf("start t_child process pid:%d\n", getpid());
    for (size_t i = 0; argv[i] != NULL; i++)
    {
        printf("传入变量:%s\n", argv[i]);
    }
    for (size_t i = 0; environ[i] != NULL; i++)
    {
        printf("环境变量:%s\n", environ[i]);
    }
    printf("t_child结束\n");
    return 0;
}
dony15$ gcc -o t_child.out t_child.c 
dony15$ gcc -o main main.c
dony15$ ./main 
start t_child process pid:9380
传入变量:abc
环境变量:name=abc
环境变量:age=15
t_child结束

六、Linux ELF 可执行文件

elf格式为可执行文件,python、bash、自定义写的c.out等都是

readelf 查看elf信息指令

readelf -a main.out

总结

注意:
char *argv[] 中argv是常量,不可以argv++
char **environ; 中environ是变量,可以environ++

向量表:地址空间连续的数组
列表:地址空间可能不连续的数组

  1. 僵尸进程:进程已经终止,进程资源没有回收
  2. 孤儿进程:父进程已终止,子进程没有回收,这种子进程需要过继给另一个进程。
    1.ubuntu 15.04版本之前,过继给init进程(孤儿院)
    2.ubuntu 15.04版本之后,过继给upstart进程(孤儿院)

exec家族函数,无论是环境变量列表、数组,还是入参变量列表、数组,结尾必须要加NULL

本章主要为C语言笔记-18-Linux基础-进程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值