Linux进程的介绍代码解析

Linux介绍进程的概念和代码编写

前言

什么是程序编译后产生的,格式为ELF的,存储于硬盘的文件

什么是进程程序中的代码和数据,被加载到内存中运行的过程

程序是静态的概念,进程是动态的概念

进程的复刻(fork)

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

1\ 父子进程的内存空间的内容是一致的,但分属不同的区域各自独立
2\ 当进程退出时(不管是主动退出还是被动退出),进入僵尸态(EXIT_ZOMBIE),僵尸态下的进程无法运行,也无法被调度,但其所占据的系统资源未被释放。
3\ 僵尸态进程要等待其父进程对其资源进程回收后,才能变成死亡态(EXIT_DEAD)

进程的创建

#include <sys/types.h>
#include <unistd.h>// 在父进程中,pid将是子进程的PID
// 在子进程中,pid将是0
pid_t fork(void);//所有的代码、变量都会复制成两份。

父子进程是并发执行的,没有先后次序,若要控制次序,要依赖于信号量、互斥锁、条件量等其他条件。
gec@ubuntu:$ ./a.out
[5140]: fork之前
[5140]: pid=5141
gec@ubuntu:$ [5141]: pid=0
在父进程5140输出后,bash立即输出了命令行提示符"gec@ubuntu:$",把子进程的输出信息挤到了后面,这是因为在bash中只要判断其子进程5140退出了,它就立即输出命令行提示符,而不管它是否还有孙子进程还在运行。从中我们也知道,bash是本程序中父进程的父进程,这三个进程的关系实际上是祖孙关系:
bash-┬ (终端shell进程)

a.out-┬ (程序中的父进程,是bash的子进程)

a.out (程序中的子进程,是bash的孙子进程)
进程的回收

#include <sys/types.h>
#include <sys/wait.h>//阻塞当前进程
pid_t wait(int *wstatus);//等待其子进程退出并回收其系统资源;

若wstatus指针为NULL,则代表当前进程放弃其子进程的退出状态。
示例代码:

#include <unistd.h> 
#include <sys/wait.h> 
int main()
{
    if(fork() == 0)
    {
        printf("[%d]: 我将在3秒后正常退出,退出值是88\n", getpid());
        for(int i=3; i>=0; i--)
        {
            fprintf(stderr, " ======= %d =======%c", i, i==0?'\n':'\r');
            sleep(1);
        }
        exit(88);
    }
    else
    {
        printf("[%d]: 我正在试图回收子进程的资源...\n", getpid());
        int status;
        wait(&status); //等待子进程执行完
        if(WIFEXITED(status))
        {
            printf("[%d]: 子进程正常退出了,其退出值是:%d\n", getpid(), WEXITSTATUS(status));
        }
    }
}

执行结果是:
gec@ubuntu:$ ./a.out
[3611]: 我正在试图回收子进程的资源…
[3612]: 我将在3秒后正常退出,退出值是88
======= 0 =======
[3611]: 子进程正常退出了,其退出值是:88
gec@ubuntu:$

宏功能WIFEXITED(status)判断子进程是否正常退出WEXITSTATUS(status)获取正常退出的子进程的退出值WIFSIGNALED(status)判断子进程是否被信号杀死WTERMSIG(status)获取杀死子进程的信号的值

#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);

与wait()的区别:
可以通过参数 pid 用来指定想要回收的子进程。
可以通过 options 来指定非阻塞等待。
pid作用options作用<-1等待组ID等于pid绝对值的进程组中的任意一个子进程0阻塞等待子进程的退出-1等待任意一个子进程WNOHANG若没有僵尸子进程,则函数立即返回0等待本进程所在的进程组中的任意一个子进程WUNTRACED当子进程暂停时函数返回>0等待指定pid的子进程WCONTINUED当子进程收到信号SIGCONT继续运行时函数返回
注意:options的取值,可以是0,也可以是上表中各个不同的宏的位或运算取值。
加载并执行指定程序

#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...);
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[]);

给进程加载指定的程序,如果成功,进程的整个内存空间都被覆盖。
各个后缀字母的含义:
l : list 以列表的方式来组织指定程序的参数
v: vector 矢量、数组,以数组的方式来组织指定程序的参数
e: environment 环境变量,执行指定程序前顺便设置环境变量
p: 专指PATH环境变量,这意味着执行程序时可自动搜索环境变量PATH的路径
execl(const char *path, const char *arg, …) 为例,参数path是需要加载的指定程序,而arg则是该程序运行是的命令行参数
execl("./a.out", “./a.out”, “123”, “abc”, NULL);
这其中:
第一个./a.out是程序本身,第二个./a.out是第一个参数。
参数列表以NULL结尾。
示例代码:

// child.c
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <sys/wait.h> 
int main(int argc, char **argv)
{
    // 倒数 n 秒  n=3
    for(int i=atoi(argv[1]); i>0; i--)
    {
        printf("%d\n", i);
        sleep(1);
    }
    // 程序退出,返回 n
    exit(atoi(argv[1])); //
}
// main.c
#include <stdio.h> 
#include <unistd.h> 
#include <sys/wait.h> 
int main()
{
    // 子进程
    if(fork() == 0)
    {
        printf("加载新程序之前的代码\n");
        // 加载新程序,并传递参数3
        execl("./child", "./child", "3", NULL);
        printf("加载新程序之后的代码\n");//没有打印
    }
    // 父进程
    else
    {
        // 等待子进程的退出  -1:任意进程
        int status;
        int ret = waitpid(-1, &status, 0);
        if(ret > 0)
        {
            if(WIFEXITED(status))
                printf("[%d]: 子进程[%d]的退出值是:%d\n",
                        getpid(), ret, WEXITSTATUS(status));
        }
        else
        {
            printf("暂无僵尸子进程\n");
        }
    }
}

程序运行结果:
gec@ubuntu:$ gcc child.c -o child //生成./child
gec@ubuntu:$ gcc main.c -o main
gec@ubuntu:$ ./main
加载新程序之前的代码
3
2
1
[5634]: 子进程[5635]的退出值是:3
gec@ubuntu:$
程序解析
子进程中加载新程序之后的代码无法运行,因为已经被覆盖了。
waitpid()中指定了options的值为0,意味着阻塞等待子进程,效果跟直接调用wait()相当。

僵尸进程

产生的原因
以下代码可以查看处于僵尸态的进程:

// zombie.c
int main()
{
    // 子进程退出(变僵尸)
    if(fork() == 0)
        return 0;
    // 父进程不退出
    pause();
    return 0;
}

执行上述代码,并使用ps命令查看进程状态:
gec@ubuntu:~$ ./zombie
gec@ubuntu:~$ ps ajx
… …
8390 8422 8422 8422 pts/2 8439 Ss 1000 0:00 bash
8422 8439 8439 8422 pts/2 8439 S+ 1000 0:00 ./zombie
8439 8440 8439 8422 pts/2 8439 Z+ 1000 0:00 [zombie]
8406 8441 8441 8406 pts/1 8441 R+ 1000 0:00 ps ajx
gec@ubuntu:~$ ps ajx

由上述结果可见,zombie父进程处于睡眠状态[S+],而其子进程[zombie]处于僵尸态[Z+]。
释放僵尸进程
如果父进程一直对其子进程不管不顾,那么其子进程的确会长期处于僵尸态,浪费系统资源。僵尸进程只有当以下情形之一发生时,才会释放其资源:
父进程对其调用 wait()/waitpid()。
父进程退出,被孤儿进程组收养。
子进程等待父进程对其执行wait()/waitpid()
释放对应僵尸子进程的系统资源
获取对应僵尸子进程的退出状态
阻塞父进程(可选)
只要父进程代码做如下修改即可避免僵尸的产生:
// no-zombie.c

int main()
{
    // 子进程退出(变僵尸)
    if(fork() == 0)
        return 0;
    // 父进程调用wait()释放子进程资源
    wait(NULL);
    pause();
    return 0;
}
子进程主动告知父进程前来收尸
具体而言,子进程在进入僵尸态时,会自动向父进程发送信号SIGCHILD,而父进程可以利用异步信号响应函数来及时处理这些僵尸子进程。参考代码如下:
void cleanup(int sig)
{
    // 僵尸子进程会被自动清除
    wait(NULL); //这个函数回收子进程
}
int main()
{
    // 在产生子进程之前,准备好处理它们的SIGCHILD信号
    signal(SIGCHLD, cleanup);
    // 子进程退出,成为僵尸进程
    if(fork() == 0)
        return 0;
    // 父进程干自己的活,无需关注子进程
    while(1)
        pause();
//由于pause()函数会在收到信号后退出
//为了能查看父进程在清除子进程资源后仍在运行
//上述代码中使用了循环来执行pause()函数
    return 0;
}
计算进程个数:
pid_t p1 = fork();
pid_t p2 = fork();
pid_t p3 = fork();

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qt历险记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值