Linux进程状态

进程的生老病死

进程状态

说进程是动态的活动的实体,指的是进程会有很多种运行状态,一会儿睡眠、一会儿暂停、一会儿又继续执行。下图给出Linux 进程从被创建(生)到被回收(死)的全部状态,以及这些状态发生转换时的条件:

请添加图片描述

进程与程序

1、程序通常时一个静态的可执行文件。
2、进程是程序运行动态体现。
3、在windos下可执行文件类型是exe; 通常在Linux下可执行文件类型是ELF,Linux中可执行文件没有固定后缀。
4、进程是系统管理的最小单位。

ELF格式文件类型

1、可执行文件
2、可重定位文件:.o .a (静态库)
3、共享目标文件:.so (动态链接库)
4、核心转储文件:.dump

// 查看可执行文件a.out的头部信息
readelf -h a.out

struct task_struct结构体位置:
/usr/src/linux-headers-4.15.0-142-generic/include/linux/sched.h

查看进程的命令

pstree // 查看系统进程树
ps -ef // 查看系统中当前进程
top - H // 动态查看系统中当前进程
htop // 动态查看系统中当前进程(需要自行安装插件)
// sudo apt-get install htop

init属于整个系统中最顶层的进行(没有父进程)
其余进程都有父进程

创建子进程——fork()

  • 功能:创建一个新的进程
  • 头文件:#include <unistd.h>
  • 原型:pid_t fork(void);
  • 返回值
    • 成功0或者大于0的正整数
    • 失败-1
  • 注:该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为 0,在原来的父进程中其返回值为大于0的正整数,该正整数就
    是子进程的 PID

请添加图片描述

要着重注意的几点:
  fork( )会使得进程本身被复制(想想细胞分裂),因此被创建出来的子进程和父进程几乎是一模一样的,说“几乎”意味着子进程并不是 100%为一份父进程的复印件,他们的具体关系如下:
  父子进程的以下属性在创建之初完全一样,子进程相当于搞了一份复制品:

  1. 实际 UID 和 GID,以及有效 UID 和 GID。

  2. 所有环境变量。

  3. 进程组 ID 和会话 ID。

  4. 当前工作路径。除非用 chdir()加以修改

  5. 打开的文件。

  6. 信号响应函数。

  7. 整个内存空间,包括栈、堆、数据段、代码段、标准 I0 的缓冲区等等。

而以下属性,父子进程是不一样的:

  • 进程号 PID。PID 是身份证号码,哪怕亲如父子,也要区分开
  • 记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。
  • 挂起的信号。这些信号是所谓的“悬而未决”的信号,等待着进程的响应,子进程也不会继承这些信号。

​ 子进程会从 fork( )返回值后的下一条逻辑语句开始运行。这样就避免了不断调用fork()而产生无限子孙的悖论。
​ 父子进程是相互平等的:他们的执行次序是随机的,或者说他们是并发运行的,除非使用特殊机制来同步他们,否则你不能判断他们的运行究竟谁先谁后。
​ 父子进程是相互独立的:由于子进程完整地复制了父进程的内存空间,因此从内存间的角度看他们是相互独立、互不影响的。

例:

int main(int argc,char *argv[])
{
    //1、 num 在父子进程中对应的虚拟内存地址相同,
    // 但不是同一空间,因此在父进程中修改num,不会影响子进程中num的值
    //int num = 10;

    // 2、堆空间和数据也会被子进程复制,但实际空间相互独立,操作互不影响
    //int* ptr = (int*)malloc(4);
    //*ptr = 11;
    
    // 3、文件操作
    // 子进程会赋值当前的文件描述符,当父进程读取一段数据之后,子进程再进行读取时,文件指针已经偏移。
    // 原因:num、ptr堆栈数据的存在与否会受当前程序影响,而文件的存在通常不受当前程序影响。
    int fd = open("head.h", O_RDWR);

    pid_t pid = fork();

    if (pid < 0)
    {
        perror("fork fail");
        return -1;
    }

    if (pid == 0) // 子进程
    {
        //1、
        //sleep(1);
        //printf("I'm Son! &num = %p\n", &num);

        //2、
        //sleep(1);
        //printf("son ptr=%p, *ptr=%d\n", ptr, *ptr);

        //3、
        printf("\nson fd = %d\n", fd);
        char buf[100];
        bzero(buf, sizeof(buf));
        read(fd, buf, 80);
        puts(buf);
    }

    if (pid > 0) // 父进程
    {
        //1、
        //num++;
        //printf("I'm Parent! &num = %p\n", &num);

        //2、
        //*ptr = 100;
        //printf("parent ptr=%p, *ptr=%d\n", ptr, *ptr);

        //3、
        printf("\nparent fd = %d\n", fd);
        char buf[100];
        bzero(buf, sizeof(buf));
        read(fd, buf, 80);
        puts(buf);
    }

    pause();

    return 0;
}

进程中加载新的程序文件——execl()

  • 功能:在进程中加载新的程序文件或者脚本,覆盖原有代码,重新运行

  • 头文件:#include <unistd.h>

  • 原型:int execl(const char *path, const char *arg, …);

  • 参数:

    • file:即将被加载执行的 ELF 文件或脚本的名字
    • arg:以列表方式罗列的 ELF 文件或脚本的参数
    • argv:以数组方式组织的 ELF文件或脚本的参数
    • envp:用户自定义的环境变量数组
  • 返回值

    • 成功:不返回
    • 失败:-1

注:

  1. 被加载的文件的参数列表必须以自身名字为开始,以 NULL为结尾。比如要加载执行当前目录下的一个叫做 a.out 的文件,需要一个参数"abcd",那么正确的调用应该是:
    execl(“./a.out”, “a.out",“abcd", NULL),
    或者:
    const char *argv[3]= {“a.out", “abcd”, NULL};
    execv(“./a.out”, argv);
  2. exec函数簇成功执行后,原有的程序代码都将被指定的文件或脚本覆盖,因此这
    些函数一旦成功后面的代码是无法执行的,他们也是无法返回的。

例:

int main()
{
    // 创建一个子进程
    pid_t pid = fork();
    if (pid == 0)
    {
        // 子进程
        //execl("/bin/ls", "ls", "-l", NULL);

        printf("我还在!\n"); // 无法被执行
        sleep(5);
        printf("我走了!\n"); // 无法被执行
    }
    if (pid > 0)
    {
         父进程
        //while(1)
        //{
        //    puts("hello");
        //    sleep(1);
        //}
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值