Linux系统编程(四)—— 进程基本知识

news2024/11/25 14:36:23

一、进程标识符pid

1、pid的类型:pid_t

pid有符号的16位整型数,也就是说可以同时进行三万多进程

2、命令:ps

ps命令用于报告当前进程的信息:
在这里插入图片描述
ps命令有着不同的组合,可以显示进程不同的内容:

在这里插入图片描述

(1)ps -axf :是描述当前进程

在这里插入图片描述

(2)ps -axm :描述进程详细信息,m表示more

在这里插入图片描述

(3)ps ax -L :以linux特有的方式进行查看

在这里插入图片描述

3、进程号是顺次向下使用

注意: 之前讲的文件描述符,是优先使用当前最小的文件描述符,但是进程标识符不一样,进程号是顺次向下使用, 即使前面有释放的,进程号也会一直变大。

4、如何获得当前进程的进程号?

(1)getpid( )函数:获得当前进程的进程号

(2)getppid()函数:获得当前进程的父进程进程号

在这里插入图片描述

二、进程的产生

1、fork( )函数

  • (1)作用:创建一个新的进程,通过复制当前进程
    在这里插入图片描述
    注意理解关键字: duplicating, 意味着拷贝,克隆,一模一样

重点1:fork创建新的进程,是通过复制当前进程

复制标志着: 副本和原本是一样的, 是通过复制父进程的方式来创建进程的,连执行到的位置都一样。

  • (2)fork后父子进程的区别
    1)fork的返回值不一样
    2)pid不同
    3)ppid不同, 父进程ID
    4)未决信号(悬而未决)和文件锁不继承
    5)资源利用量归零

  • (3)init进程: 1号进程,是所有进程的祖先进程

  • (4)fork( )函数返回值
    fork( )函数运行成功后,会有两个返回值。当调用成功的时候,给父进程返回子进程的pid号, 返回给子进程0;如果创建失败,则返回父进程-1
    在这里插入图片描述

  • (5)例子:

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

int main()
{
    printf("[%d] begin!\n", getpid());

    pid_t pid = fork();
    
    if(pid < 0){ 
        perror("fork()");
        exit(1);
    }   
    
    if(pid == 0){    // child
        printf("[%d]: Child is working~\n", getpid());
    }else{           // parent
        printf("[%d]: Parent is working~\n", getpid());
    }   

    printf("[%d] End!\n", getpid());
    
    exit(0);

}

运行结果:
在这里插入图片描述
注意:永远不要去猜父进程和子进程哪一个被调度。调度器的调度策略来决定哪个进程先运行。这里也可能会出现子进程先调度。

重点2:永远不要去猜父进程和子进程哪一个被调度

如果想要子进程先运行,可以有一定的措施,但一定不要猜测(比如这里加了一个sleep函数-但是不推荐用这个函数,后面会解释)
在这里插入图片描述
运行结果:
在这里插入图片描述
在源代码中加入一句 getchar( )代码,让程序一直运行,不结束:
在这里插入图片描述
另外开一个终端,利用 ps -axf 命令,来查看当前进程的信息:
在这里插入图片描述
像这种阶梯状的都是父子进程的关系,这种顶格写的他们的父进程都是1号init

重点3:面试题->fflush的重要性

(begin是打印了一个,end一定是打印了两个,说明两个进程执行到的节点都是一样的)但是,如果将输出重定向到一个文件中,就会出现问题:
可以看到,这里出现了两个 Begin
在这里插入图片描述
为什么呢? 是因为在创建子进程的时候没有刷新缓冲区,导致,缓冲区中的数据没有更新。因此应该这样做:在fork()之前刷新所有该刷新的流
标准输出是行缓冲,文件是全缓冲,所以会出现打印终端上没有问题,但是输出到文件中时就有两个Begin) (全缓冲模式中 \n 只表示换行,没有刷新缓冲区的作用
在这里插入图片描述
运行结果:
在这里插入图片描述

2、例子:数质数的个数

(1)原始例子

题意:计算给定区间中质数的个数
代码:

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

#define LEFT   30000000
#define RIGHT  30000200

int main() {
    int i, j, mark;
    for (i = LEFT; i <= RIGHT; i++) {
        mark = 1;
        for (j = 2; j < i / 2; j++) {
            if (i % j == 0) {
                // 非质数
                mark = 0;
                break;
            }   
        }   
        if (mark)
            // 是质数
            printf("%d is a primer\n", i); 
    
    }   
    exit(0);
}

运行结果:
在这里插入图片描述

如果想要知道该程序执行了多少的时间,可以采用:time 命名

在这里插入图片描述

如果终端上只想显示运行了多少时间,但是代码却又输出了内容,可以将这些内容输出重定向到一个空设备上去:/dev/null (注意这里 time 不属于标准输出,不能重定向到空设备上去)
在这里插入图片描述

(2)改进例子

  • 如果把这个任务让多个子任务去操作, 创建200 个子进程去计算
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define LEFT   30000000
#define RIGHT  30000200

int main() {
    int i, j, mark;
    pid_t pid;
    
    for (i = LEFT; i <= RIGHT; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork");
            exit(1);
        }   

        if (pid == 0) {
            // i子进程,干活
            mark = 1;
            for (j = 2; j < i / 2; j++) {
                if (i % j == 0) {
                    // 非质数
                    mark = 0;
                    break;
                }   
            }
            if (mark)
                // 是质数
                printf("%d is a primer\n", i);
            exit(0); // 子进程结束(一定要有)
        }
    }
    exit(0);
}

运行结果:
(可以看到这是乱序的状况,这是因为进程调度的问题)

在这里插入图片描述

(3)对比

在这里插入图片描述

  • 可以看到前后的时间差距很大,你可能会想,程序0相当于一个人在干201个活, 程序1相当于一个人干一个活,共201个人干, 所以它的时间短,错了!
    因为当前系统是4核的,所以4个进程是可以平行运行的。如果是单核机器的话, 无论是多少个 进程,都要一个一个调度,所以单核机器使用的时间大小和有多少个进程没有关系

  • 注意:但是四核的机器运行的时间并不是单核机器运行时间的四分之一,所以运行时间还是和调度有很大关系。

  • 谁打开谁关闭, 谁申请,谁释放,父进程创建了子进程,那么父进程就是要给子进程“收尸

  • 如果程序当中出现僵尸态,僵尸态应该是一闪即逝的,因为表示及时收尸了; 或者如果你查看的时候有几个僵尸态进程,过了三五秒钟再看,还是有几个僵尸态,但是换了一批,这种情况表示要么就是当前操作系统比较忙,要么就是父进程比较忙,需要过一段时间进行批量的收尸。

3、vfork( )函数:将被废弃了

  • fork( )函数:是通过复制父进程的方式产生子进程,它和父进程一样,只有那五点不一样。

  • 例如,我的父进程中从数据库中导入30万条记录,创建子进程,然后让子进程打印hello world, 然后退出,这样的fork成本比较高,但是注意,这说的只是fork的原始实现

  • 如果使用vfork的话,当使用vfork产生一个子进程的话,子进程和父进程共用一个数据块,但是,如果子进程中修改了的,父进程可以看得到吗??man手册中说:使用vfork调用的进程,只能保证成功的调用_exit(2)或者exec(3) , 其他的都属于未定义范围。
    在这里插入图片描述

  • 其实现在fork已经不是前面说的那样了,fork添加了写时拷贝技术,在进行fork的时候,子进程与父进程确实是共用数据块(只读不写), 如果一个进程企图使用一个指针去写,首先会把数据拷贝一份,然后修改自己的那一份,不会影响其他的进程,谁改谁拷贝

三、进程的消亡及释放资源(收尸)

  • 收尸:等进程状态发生变化,然后对进程收尸,资源回收。

    前面执行primer1程序的时候,命令行会先出现,然后后面的打印后出现,这个现象的原因是???

1、wait( )函数(没有指向,只会死等)

在这里插入图片描述

(1)功能

父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
1) 阻塞等待子进程退出
2) 回收子进程残留资源
3) 获取子进程结束状态(退出原因)

(2)查看进程退出状态

pid_t wait(int *status) 将当前子进程收尸收回来的状态放入到一个整形变量status中,也就是说给他一个整形变量的地址值。函数提供了一系列的宏,用于查看当前进程退出的状态是什么
在这里插入图片描述

  • WIFEXITED(status) :判断当前进程是否正常终止,背五条正常终止,三条异常终止

  • WEXITSTATUE(status) :在进程必须正常结束情况下,返回子进程退出时的状态

  • WIFSIGNALED(status) :判断进程是否是由信号使其异常终止的

  • WTERMSIG(status):如果是由信号导致的进程异常终止,返回使进程终止的那个信号的编号

2、waitpid( )函数(不用死等)

在这里插入图片描述

(1)作用

作用同于wait,但可指定pid进程清理,可以不阻塞。
该函数好用的点在于可以使用 options 使得操作不阻塞(options是一个位图,其中一个NOHANG. 不用死等)

(2)参数介绍

  • 参数 pid:
    pid > 0 回收指定ID的子进程

    pid = -1 回收任意子进程(相当于wait)

    pid = 0 回收和当前调用waitpid一个组的所有子进程

    pid < -1 回收指定进程组内的任意子进程
    在这里插入图片描述
    注意:
    (1)用户分组,进程组什么的,不管什么分组,唯一的好处就是好操作。一个进程创建出来的进程是跟它的父进程是同组进程(当然可以人为设置使其不是同组)。
    (2)一次wait或waitpid调用只能清理一个子进程,清理多个子进程需要用到循环

  • 参数 status
    同wait函数一样,这里不做介绍了

  • 参数 options
    该参数是一个位图,可以由多个进行 与 操作,options的默认值是0,表示会阻塞。可以通过将 options 设置为常量 WNOHANGWUNTRACEDWCONTINUED
    的各种组合来修改默认行为:

    (1)WNOHANG: 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。 默认的行为是挂起调用进程,直到有子进程终止 。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。(这个就可以使回收操作变成非阻塞)

    (2)WUNTRACED: 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止 。返回的PID 为导致返回的已终止或被停止子进程的 PID。默认的行为是只返回已终止的子进程。当你想要检査已终止和被停止的子进程时,这个选项会有用。

    (3)WCONTINUED: 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一 个被停止的进程收到 SIGCONT 信号重新开始执行。

3、例子:利用wait( )函数进行收尸

代码:

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

#define LEFT   30000000
#define RIGHT  30000200

int main() {
    int i, j, mark;
    pid_t pid;

    for (i = LEFT; i <= RIGHT; i++) {
        pid = fork();
        if (pid < 0) {
            perror("fork");
            exit(1);
        } 
        if (pid == 0) {
            // i子进程,干活
            mark = 1;
            for (j = 2; j < i / 2; j++) {
                if (i % j == 0) {
                    // 非质数
                    mark = 0;
                    break;
                }
            }
            if (mark)
                // 是质数
                printf("%d is a primer\n", i);
            exit(0); // 子进程结束
        }
    }
    
    // int st;
    for (i = LEFT; i <= RIGHT; i++) {
        // wait(&st);
        wait(NULL); // 不保存状态)
    }
    exit(0);
}

运行结果:
在这里插入图片描述

四、进程分配

问题:如果当前的任务有201个,然后用于处理这个任务的进程有N(3)个,如何分配?

1、分块法

  • 三个进程,第一个进程处理一部分,第二第三个进程处理一部分。

  • 缺点是:任务的轻重,分配并不平均

2、交叉分配法

  • 第一个数给进程1, 第二个数给进程2, 第三个数给3, 第四个数给进程1, 第五个数给进程2…

  • 如果在一个任务中,如果可以使用分块法也可以使用交叉分配法的时候,我们使用交叉分配法。

例子:利用交叉分配法对任务进行分配

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

#define LEFT   30000000
#define RIGHT  30000200
#define N      3

int main() {
    int i, j, n, mark;
    pid_t pid;
    
    for (n = 0; n < N; n++) {
        pid = fork();
        if (pid < 0) {
            perror("fork()");
            // 这里注意,如果出错了,需要写一个循环,把曾经fork出去的内容全部收尸
            exit(1);
        }  
        
        if (pid == 0) {
            for (i = LEFT + n; i <= RIGHT; i += N) {
                mark = 1;
                for (j = 2; j < i / 2; j++) {
                    if (i % j == 0) {
                        // 非质数
                        mark = 0;
                        break;
                    }
                }
                if (mark)
                    printf("进程[%d] %d is a primer\n",n , i);
            }
            exit(0);
        }
    }

    for (n = 0; n < N; n++) {
        wait(NULL);
    }
    exit(0);
}

运行结果:
在这里插入图片描述

3、池类算法

  • 上游一些进程,下游一些进程,中间一个模块(池子),上游的进程将任务往中间模块扔,下游的进程抢任务。

  • 这样的任务分配和抢到的任务都具有随机性。

五、exec函数族

  • 在当前阶段,需要记住一个单词 "few", 这个单词的三个字母: f: forke: exec, w: wait, 这三个函数搭建起了linux的框架。

  • 疑问?? 为什么shell创建的子进程不是shell, 而是primerN这样的进程??

  • exec函数族的函数有(执行一个文件):execl( )、execp( )、execle( )、execv( )、execvp( ) 。

  • exec函数族用一个新的进程映像,替换当前的进程映像。
    在这里插入图片描述

  • 比如说: 进程空间搭建起来的话,在exec这个阶段就已经有代码段,已初始化数据段,未初始化数据段,栈和堆是后来才搭建起来,所以在c程序虚拟空间完成的时候,是在各个不同的阶段做的不同的实现,搭建起来的不同的数据内容。
    在这里插入图片描述

  • 注意: 上面的environ环境变量, 它的存储和argv的存储非常像。后面两个函数看起来是定参结构,前面两个是变参实现,但是实际上有多少个存储结构是和argv有关的,argv才是真正意义上的变参实现,所以前面两个函数是定参,后面两个是变参。

1、execl( ):定参

  • 格式: int execl(const char *pathname, const char *arg, ... /* (char *) NULL */);

  • 传入一个可执行文件的路径,char * arg …的意思是:要给这个命令传参的化,参数是哪些,可以传多个参数,最后补一个NULL作为当前传参的结束。

2、execlp( ):定参

  • 格式:int execlp(const char *file, const char *arg, ... /* (char *) NULL */);

  • 传入一个可执行文件的名字,char * arg …的意思是:要给这个命令传参的化,参数是哪些,可以传多个参数,最后补一个NULL作为当前传参的结束。

  • 为什么只传入一个可执行文件的名字不需要路径就可以呢??因为他有环境变量。环境变量是程序员与管理员之间的一种约定。

3、execle( ):定参

  • 格式:int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

  • 这个函数和execlp差不多,最后可以将一个环境变量导入进来。

4、execv( ):变参

例子:查看时间戳

  • (1)执行命令行打印时间戳
    在这里插入图片描述

  • (2)利用 which 命令查看一下 date 命令在哪里
    在这里插入图片描述

  • (3)利用代码打印时间戳

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

/*
    date + %s
    打印时间戳
*/
int main() {
    puts("Begin");

    // 用一个新的进程映像来替换现在的
    // 如果下面的execl()函数执行成功,则不会打印End
    execl("/bin/date", "date", "+%s", NULL);
    // 这里都不用判断execl返回值,因为下面的内容只有在
    // execl执行失败之后才会执行到
    perror("execl()");
    exit(1);
    puts("End");
    exit(0);
}

执行结果:
在这里插入图片描述

  • (4)我们将这个函数的输出重定向到 /tmp/out , 然后在显示,会发现Begin没有了
    在这里插入图片描述
    所以需要注意:在exec这个函数族的使用的时候,一定也要注意fflush的使用。当缓冲区还没有向外输出呢,exec这个函数就用来替换当前的旧的进程映像。所以在使用exec之前一定要将所有的流刷新一下。
    在这里插入图片描述

  • (5)你还是你,但是你已经不是你了,它的壳子没有变(PID), 但是它的内容(进程映像)已经变了。Unix 世界是怎么做的, shell

    代码:


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

/*
    使用fork, wait, exec
*/

int main() {
    pid_t pid;
    puts("Begin");
    fflush(NULL);
    
    pid = fork();
    if (pid == 0) {
        // 子进程
        execl("/bin/date", "date", "+%s", NULL);
        perror("execl()");
        exit(1);
    }   
    // 父进程等着收尸
    wait(NULL);
    puts("End");
    exit(0);
}

运行结果:
在这里插入图片描述
所以所有的shell都是这样做的,当你执行一个命令的时候,shell会根据fork创建一个子进程,然后在子进程里面进行exec操作, 替换子进程,shell这个父进程在wait, 等待收尸。所以当我们执行一个命令的时候,都是命令的结果先出来,也就是子进程结果出来,然后命令行再弹出来,因为shell进程在wait,当子进程结束的时候,shell帮忙给收尸。

为什么是父子进程可以打印在同一终端上

  • 每个进程中有一个文件描述符表,0、1、2关联stderr, stdin, stdout, 一旦创建子进程,子进程是通过复制父进程来的,所以它的文件描述符表是和父进程一样的,所以终端(父进程)执行 一个命令(子进程)的时候,子进程是打印输出到终端的。

例子:执行sleep

代码:

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

/*
    使用fork, wait, exec
*/

int main() {
    pid_t pid;
    puts("Begin");
    fflush(NULL);

    pid = fork();
    if (pid == 0) {
        // 子进程
        execl("/bin/sleep", "sleep", "100", NULL);
        perror("execl()");
        exit(1);
    }   
    // 父进程等着收尸
    wait(NULL);
    puts("End");
    exit(0);
}

查看当前父子进程关系:
在这里插入图片描述

  • argv[0]很多时候被人忽略,但是如果换成其他的名字,比如,我们换为"httpd", 显示的就是httpd, 这就是木马的产生的低级办法。比较高级的隐藏在内核模块当中,当然没有隐藏在用户态危害更大。
    在这里插入图片描述父子进程关系在这里插入图片描述

六、用户权限及组权限

  • 其实用户权限和组权限是没有我们想的那么简单,它们是分作好几种方式来进行存放的,为什么这么做?

  • 当我们查看/etc/shadow的时候,用户权限是不够的;但是我们操作/etc/passwd权限是够的。
    在这里插入图片描述

  • 一开始的时候,普通用户什么权限也没有,连修改自己口令的权限都没有,权限全部集中在root用户,慢慢的,root用户的权限开始下放,普通用户才慢慢的有了权限。普通用户可以通过passwd文件修改自己的口令。

1、关于:u + s 和 g + s

  • 其实在我们执行某一个命令的时候,是带着一个身份来执行的,身份从哪来的?

  • uid 和 gid, 拿出一个来讲: uid
    user id(uid)其实存的不只有一份,它有三份。一个叫real uid, effecitive uid, save uid。可以没有save uid . 鉴定权限的时候是用effective id

  • exec鉴定权限, exec发现了u+s的权限, 看/etc/passwd 的权限。
    在这里插入图片描述
    前面讲到stat函数的时候,其中也有一位用来保存当前文件时否有u+s, g+s的权限,(s体现u+s权限, x体系g+s权限)

  • 注意:
    (1) u+s指的是: 如果一个文件有u+s的权限,那就意味着:当别的用户在调用当前这个二进制文件的时候,它的身份会切换成当前二进制文件的user的权限来执行。

    (2)g+s指的是:如果这个文件有g+s权限的话,那就意味着,当前不管任何用户来调用这个二进制可执行文件的时候,当前用户的身份就会切换成这个二进制文件的同组用户身份来执行。

    (3)exec来鉴定权限, 鉴定权限看的是effective ui。

    (4)所以当用:ls -l /usr/bin.passwd 的时候,其实是以root身份来跑。
    (5)其实u+s, g+s的作用 就是将原来root的权限打散往下放。

2、当前shell的身份从哪里来的

  • 当前机器中产生的第一个进程是init进程,当init产生的时候,本身还是root权限,所以init的权限是root权限
  • 然后init 进行fork+exec产生一个进程getty进程(root身份),getty进程提示你请输入login name, 然后我们输入用户名
  • 然后getty进程进行exec(注意没有fork子进程), 直接替换为login 进程,然后login进程提示你继续输入 passwd.
  • 如果口令验证成功,fork+exec 产生一个shell (这个shell是在passwd中根据用户名和密码找得到shell存在的路径)。
  • 如果失败,继续返回回去输入用户名密码。
  • shell身份固定以后再继续做任何时候,都是带着用户权限的身份去做的了
    在这里插入图片描述

3、权限相关的常用函数

(1)getuid( ) 返回进程实际进程ID

(2)geteuid( ) 返回进程的有效ID effective uid

(3)getgid( ) 获取当前进程的真实组ID

(4)getegid( ) 返回当前进程有效的组ID effective gid

(5)setuid( ) 设置有效ID

(6)setgid( ) 设置有效的组ID

(7)setreuid( ) 交换ruid 和 euid

(8)setregid( ) 交换rgid 和 egid

(9)seteuid( )

(10)setguid( )

七、解释器文件(脚本文件)

  • 解释器文件是有Unix代表性的味道,因为Unix在讲机制,策略,总是在告诉你我能怎么做,我在怎么做,我能完成什么样的功能,但是不会告诉你我能做到什么程度。

什么叫脚本文件

  • 脚本文件总是有一个脚本文件的标记,脚本文件其实不在乎后缀是什么,一般我们写shell脚本叫a.sh, Python脚本的话叫a.py, 当然这个后缀没什么关系

  • 脚本文件的标记就是文件顶头有一个 #! , 下面是你要用什么, 比如说 /bin/bash
    创建一个名为 aa.123 的脚本文件

#!/bin/bash

ls
whoami
cat /etc/shadow
ps
  • 注意: 中间一条命令的执行失败不shell影响其他的命令执行

我们看下aa.123 的属性:
在这里插入图片描述
然后给他改变权限, 给他可执行权限 chmod u+x aa.123
在这里插入图片描述
去执行这个脚本:
在这里插入图片描述
刚刚这个脚本文件等同于下面的操作:
在这里插入图片描述
其中的 test 文件为:
在这里插入图片描述

  • 脚本的优点就是: 我们有时候用c程序要写好大一段程序去解决的时候,shell几句话就解决了。不光是shell, Python脚本也一样,同时Python比shell 更灵活一些。

  • 当你的shell看到一个脚本文件的时候,它对带脚本文件和别的程序不一样,其他的程序的话,就会将整个程序装在进来,如果在装载程序的时候发现,最前面是脚本标记,也就是#! , shell就不把当前所有内容都装载进来了,只在当前shell环境下来装载解释器文件(也就是/bin/bash),然后用指定的解释器解释全部的内容,包括第一行(第一行的井号刚好就是注释)。

  • 这里的解释器不一定是shell, 我们把/bin/bash 改成/bin/cat , 结果如下:
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    当然,这也等同于:cat test
    在这里插入图片描述

八、system( )函数

在这里插入图片描述

  • 根据man手册查询可知,system( )函数就相当于一个 fork, exec, wait 的简单封装
  • system( )函数的小例子:
#include <stdio.h>
#include <stdlib.h>

int main() {
    system("date +%s > /tmp/out");
    exit(0);
}

运行结果:
在这里插入图片描述

  • 利用fork, exec, wait 的简单封装system函数:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;
    fflush(NULL);

    pid = fork();
    if (pid == 0) {
        // 子进程
        execl("/bin/sh", "sh", "-c", "date +%s > /tmp/out", NULL);
        perror("execl()");
        exit(1);
    }   
    // 父进程等着收尸
    wait(NULL);
    exit(0);
}

运行结果:
在这里插入图片描述
可以看到前后是一样的效果

九、进程时间:times( )函数

  • time ./primer1 是打印执行primer1的时间

  • 原理: 当前父进程在等子进程的时候在掐时间,所以说当前时间(等待子进程时间)包括等在子进程结束的时间,都会纳入到这个时间当中去。

  • times( ) 函数用来完成time命令的

在 man 手册中查询times( )函数:
在这里插入图片描述
计时单位:clock_t 滴答数, 一秒钟的滴答数可以用一个宏来检测,如下:
在这里插入图片描述
tms的四个时间碎片拼一起就是一个time命令。

十、守护进程

  • 守护进程是生存期较长的一种进程,一般在系统启动时启动,系统关闭时停止,没有控制终端,后台运行。

  • 守护进程可以理解成服务,一些系统级模块,开机启动的时候,需要进程一直在后台跑的,这种程序被称为守护进程,比如:Httpd守护进程, dhcp(动态分配IP地址), ssh服务,等等

  • 当我们执行ps命令的时候,看到的一些后台跑的内容,有很多本身就是守护进程。无论是windows还是linux, 都是有守护进程。

  • 守护进程一般满足如下条件:
    (1)脱离控制终端(因为控制终端的输入和输出会对它有影响)
    (2)会话的leader
    (3)group的leader

1、会话 session (终端)

  • 会话是一个或多个进程组的集合

  • 我们平时接触的都是虚拟终端,实际意义上的终端是一个笨设备。只会两个操作,输入和输出。一般在银行这种要求安全性比较高的地方,可能会有真正意义上的终端存在。

  • 在学并发这个模块的时候,就会发现,进程实际上就是容器,在我们的处理器处理当前的调度的时候,其实它是以线程为单位来调度的,所以认为多线程的并发要比多进程的并发要更规范
    在这里插入图片描述

  • 进程组分为:前台进程组+后台进程组,最多可以有一个前台进程组,可以没有前台进程组。

  • 如果一个程序运行的会久一些,我不希望它占着我的命令行,终端,我还会做别的事情,那我在这条命令后面加一个地址符号,也就意味着,当前这条命令我要放到后台去运行。

  • 前台进程组能够接受标准输入,能够标准输出,后台进程组不行。如果不区分前台进程组和后台进程组,我们不知道命令行输出的是哪个进程组的。

  • 所以我们的守护进程的标准的输入和输出我们会进行重定向。

2、setsid( )设置为守护进程

在这里插入图片描述

  • 如果当前调用这个函数的进程不是一个group leader的话,会创建一个新的session。调用这个函数的进程会成为新的session的leader。其实调用setsid()进程的特点就是守护进程的特点。

  • 利用 ps axj 命令可以查看工作的进程
    在这里插入图片描述

  • TTY表示的是控制终端,守护进程是脱离控制终端的, 所以打问号?的是守护进程

  • 利用setsid()之后,当前的进程会成为session的leader, 会成为group的leader, 所以说它的PID, PGID,SID是相同的

  • 由于守护进程在执行的时候,父进程会一直在等着,比如:FTP是从开机就开始,父进程一直在等着,既然子进程调用setsid()以后会变成守护进程,直接让父进程退出,父进程不需要收尸,如果父进程退出了,那么当前守护进程的父进程的PPID值就为1, init进程

十一、系统日志

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/539187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

夜天之书 #83 Web API 的开发工具和平台

上一篇文章《Web API 简介》的落脚点是 Web API 的体验。 Web API 作为许多软件的第一道门面&#xff0c;提升其体验的努力从来没有停止过。今天&#xff0c;围绕 Web API 的开发体验和使用体验&#xff0c;已经成长出一个庞大的软件生态。本文以常用的 Web API 开发工具和平台…

什么是 Java中的零拷贝

什么是零拷贝 WIKI中对其有如下定义&#xff1a; “Zero-copy” describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. 从WIKI的定义中&#xff0c;我们看到“零拷贝”是指计算机操作的过程中&#x…

数据库迁移 | 拥抱国产化数据库openGauss

Squids DBMotion再添新库同步能力&#xff0c;本期增加了对openGauss数据库的同步支持。 openGauss数据库是一款开源关系型数据库管理系统&#xff0c;采用木兰宽松许可证v2发行。openGauss内核深度融合华为在数据库领域多年的经验&#xff0c;结合企业级场景需求&#xff0c;持…

PFTL101B 20KN 3BSE004203R1主要介绍ACS380 Modbus通讯功能

​ PFTL101B 20KN 3BSE004203R1ABB系统优化船舶性能、效率和可持续性 根据经合组织的一份报告&#xff0c;货物和人员的海上运输是全球经济活动的重要驱动力&#xff0c;到2050年将增加两倍。据国际海事组织&#xff08;IMO&#xff09;称&#xff0c;这一增长将导致该行业的温…

Hi3861 移植 LVGL

一、前言 给 Hi3861 适配了硬件 spi &#xff0c;master 模式下最高 spi 速率可以达到 40M&#xff0c;用来驱动 oled 屏幕。 适配过程遇到了一个芯片bug&#xff0c;困扰了很久&#xff0c;clk 管脚驱动能力差&#xff0c;需要外接一个上拉电阻才能正常运行。适配完成移植 lvg…

k8s系列(五)——资源控制器

k8s系列五——资源控制器 控制器的必要性 自主式Pod对象由调度器调度到目标工作节点后即由相应节点上的kubelet负责监控其容器的存活状态&#xff0c;容器主进程崩溃后&#xff0c;kubelet能够自动重启相应的容器。但对出现非主进程崩溃类的容器错误却无从感知&#xff0c;这…

阿里下放自动驾驶,汽车业务是个坑,或是时候探讨下一个乐视了

阿里发布公告指达摩院自动驾驶团队将全部并入菜鸟集团&#xff0c;虽然并没有说关闭自动驾驶业务&#xff0c;但是自动驾驶业务已不再是阿里看重的业务&#xff0c;导致如此结果在于当前汽车行业发生的重大变化。 一、传统汽车开始发力 今年4月份的新能源汽车企业销量排名数据显…

配置gitee ssh免密拉取代码-唯一客服系统文档中心

Gitee 我们的客服系统代码托管于Gitee私有仓库默认情况下只用于开发者自我代码管理&#xff0c;不对外公布。如果你也是放在私有仓库进行托管&#xff0c;可以如下配置免密操作。 部署公钥免密拉取代码 部署公钥允许以只读的方式访问仓库&#xff0c;主要用于仓库在生产服务器的…

即时通讯在线聊天APP开发解决方案

即时通讯是目前移动端最为流行的通讯方式&#xff0c;各种各样的即时通讯软件也层出不穷&#xff1b;服务提供商也提供了越来越丰富的通讯服务功能&#xff0c;打造一个实时通信系统&#xff0c;允许两人或多人使用网络实时的传递文字消息、文件、语音与视频交流。今天河北领行…

Excel中时间戳与标准日期格式的互相转换

背景 在excel中将13位毫秒级别的时间戳转换为标准的日期格式(yyyy-mm-dd hh:mm:ss.000)&#xff0c;使用如下模板 TEXT(<source_cell>/1000/8640070*36519,"yyyy-mm-dd hh:mm:ss.000") 在excel中将10位秒级别的时间戳转换为标准的日期格式(yyyy-mm-dd hh:mm:ss…

每日学习记录

GDB调试 首先是安装&#xff0c;以及普通的一些命令&#xff0c;重点是如何打断点 调试多进程和多线程 不同的gdb版本可能不是很支持&#xff0c;需要用比较新的版本>8.3 多进程 fork()函数创建一个一模一样的进程正常来说&#xff0c;同一个执行文件&#xff0c;gdb只…

使用 CNN 进行面部情绪识别

面部表情是人类之间交流的重要方式。 在人工智能研究中&#xff0c;深度学习技术已成为增强人机交互的强大工具。心理学中面部表情和情绪的分析和评估涉及评估预测个人或群体情绪的决定。 本研究旨在开发一种能够使用卷积神经网络&#xff08;CNN&#xff09;算法和特征提取技术…

【Java每日一练】总目录(2023.3.11~5.18)共69篇

2023.3.11~2023.5.18 连载两个多月共69篇&#xff0c;暂停更 Java 2023.05 2023.5.11-2023.5.18 20230518 1. 移除链表元素 &#x1f31f; 2. 跳跃游戏 II &#x1f31f;&#x1f31f; 3. 复原 IP 地址 &#x1f31f;&#x1f31f; 20230517 1. 存在重复元素 &#x…

chatgpt赋能Python-pycharm添加库

Pycharm添加库 – 提高Python编程效率的绝佳利器 Pycharm是一款强大的Python IDE&#xff0c;为广大Python开发人员提供了高度集成化的开发环境。通过Pycharm&#xff0c;我们可以充分利用各种强大的开发工具来简化开发流程&#xff0c;提高编程效率。其中一项重要的功能就是添…

SciPy库(一)常数与特殊函数与插值

一、概述 SciPy是一个开源的Python科学计算库&#xff0c;它建立在NumPy之上&#xff0c;提供了许多有用的科学计算功能。SciPy包括各种科学计算模块&#xff0c;如线性代数、优化、积分、插值、特殊函数、快速傅里叶变换、信号处理和图像处理等。SciPy库的主要特点是其高效性和…

突然放大,Midjourney 来中国了!

突然放大&#xff0c;Midjourney 来中国了&#xff01; 一、 Midjourney内测版本的推出 Midjourney&#xff0c;一款新颖的 AI 视觉艺术平台&#xff0c;近日在中国开放了内测版。这个内测版本在 QQ 频道上进行&#xff0c;每周一和周五的 18:00 开放入口&#xff0c;人数一旦满…

家里宽带申请公网 IP(二)

来源&#xff1a;公众号【鱼鹰谈单片机】 作者&#xff1a;鱼鹰Osprey ID &#xff1a;emOsprey 在之前的《家里宽带搞个服务器&#xff0c;YYDS&#xff08;一&#xff09;》笔记中大概介绍了&#xff0c;因为现有的 IPv4 的地址资源枯竭&#xff0c;导致大部分网络都是局域…

中国系统正式发声!所有用户永久免费,网友:再见了,CentOS!

点关注公众号&#xff0c;回复“1024”获取2TB学习资源&#xff01; 如果说&#xff1a;没有操作系统会怎么样&#xff1f; 对于个PC来说&#xff0c;无论是台式机、笔记本、平板等等&#xff0c;一切都变的一无是处&#xff0c;这些硬件对我们来说&#xff0c;和一堆废铁没什么…

手把手写一个简单IOC(基于XML配置文件)

目录 创建模块 准备测试阶段测试用的Bean 编写ApplicationContext接口 编写ClassPathXmlApplicationContext 确定采用Map集合存储Bean 解析配置文件实例化所有Bean 解析配置文件实例化所有Bean 测试阶段1(实例化bean) Bean的属性赋值 测试阶段2(为bean对象进行赋值)…

初阶数据结构——栈和队列习题

目录 括号匹配问题思路代码 用队列实现栈思路注意点代码 用栈实现队列思路代码 设计循环队列思路数组实现代码链表实现代码 括号匹配问题 OJ链接 思路 是左括号则入栈&#xff0c;是右括号则出栈然后两两比较 代码 #include<stdio.h> #include<stdlib.h> #i…