【Linux学习】日积月累——进程控制

news2024/11/19 1:26:29

一、进程创建

1.1 fork函数的认识

#include<unistd.h>
pid_t fork(void);
返回值:自进程返回0,父进程返回子进程PID,出错返回-1
  • 进程调用fork,当控制转移到内核中的fork代码后,内核做:
  • 分配新的内存块和内核数据结构给子进程;
  • 将父进程部分数据结构内容拷贝至子进程;
  • 添加子进程到系统进程列表中;
  • fork返回,开始调度器调度。

image-20230723162019176

图1 fork创建子进程

  当一个进程调用fork后,就有两个二进制代码相同的进程;而且它们都运行到相同的地方;但每个进程都将可以开始它们自己的旅程。

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

int main(){
    pid_t pid;
    printf("before: pid is %d\n", getpid());
    if((pid = fork()) == -1){
        perror("fork()");
        exit(1);
    }
    printf("after: pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);

    return 0;
}

调试结果:

image-20230723165613647

图2 输出结果

  从图2中可以看到:进程2981打印before消息,然后它有打印after。另一个after消息由2982打印的。注意到进程2982没有打印before,见图3:

image-20230723170343389

图3 fork创建的子进程执行

所以,fork之前父进程独立执行,fork之后,父进程、子进程两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

1.2 写时拷贝

  通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本,具体详见下图:

image-20230723164131921

图4 写时拷贝
注意:写时拷贝的本质是按需申请资源的策略。

1.3 fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的段码段。例如,父进程等待客户端请求,生成子进程来处理请求;

  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.4 fork调用失败原因

  • 系统中有太多的进程;

  • 实际用户的进程数超出了限制。

二、进程终止

2.1 进程退出场景

  • a.正常执行完(1.结果正确;2.结果不正确);

  • b.崩溃了(进程异常)【具体在进程信号处详细说明】 – 崩溃的本质:进程因为某些原因,导致进程收到来自操作系统的信号(kill -9)。

  在main()函数中,return 0;中0是进程的退出码,表示正常执行完了(1.结果正确(0);2.结果不正确(!0非零;1,2,3,4 --> 表示不同原因))-> 供用户进行进程退出健康状态的判定。

image-20230608221525174

图5 进程退出码查看
  在图5中,`echo $?`只会保留当前最近一次执行的进程的退出码。下列代码执行打印CentOS7中,所有的程序退出码:
#include<stdio.h>
#include<string.h>
int main(){
    for(int i = 0; i <= 200; i++){
        printf("%d : %s\n", i, strerror(i));
        //char* strerror(int errnum); --> #include<string.h>
    }
    return 0;
}

image-20230723223233746

图6 遍历进程退出码

  上述代码执行的是遍历Linux系统(主要指CentOS7)中进程退出码,具体如图6所示,有(0~133)即134个。

进程退出:就是操作系统内少了一个进程,操作系统就要释放进程对应的内核数据结构+代码和数据(如果有独立的)。

2.2 进程常见退出方法

2.2.1 常见的三种方法

  • main( )函数return,其他函数return 仅仅代表该函数返回 -> 进程执行,本质是main执行流执行!
  • exit()函数退出 – C标准库函数调用
  • _exit() – 系统函数调用

2.2.2 方法的刨析

exit(int code){
	//code代表就是进程的退出码,等价于main(){return xxx};
	//冲刷缓冲区
	_exit(code);
}

在代码的任意地方调用该函数都表示进程退出!,具体下列代码所示:

#include<stdlib.h>

int main(){
    for(int i = 0; i <= 140; i++){
        printf("%d : %s\n", i, strerror(i));
        exit(123);
    }
    return 0;
}

image-20230723224115899

图7 遍历进程退出码
综合return和exit的案例:
int add_to_top(int top){
    printf("enter add_to_top\n");
    int sum = 0;
    for(int i = 0; i <= top; i++){
        sum += i;
    }
    exit(213);
    printf("out add_to_top\n");
    return sum;
}

int main(){
    int result = add_to_top(100);
    if(result == 5050){
        return 0;
    }else{
        return 11;//计算结果不正确
    }
}

image-20230723225403739

图8 遍历进程退出码

注意:exit()函数退出,在代码的任意地方调用该函数都表示进程退出。

_exit()函数,貌似等价于exit(),但不会清理缓冲区

_exit(int status)

exit()函数的调用举例:

int main(){
    printf("welcome to TU Berlin\n");//输出缓冲区
    sleep(2);
    exit(107);//关闭文件+冲刷缓冲区
    return 0;
}

image-20230723230717191

图9 exit()函数调用调试结果

_exit()函数的调用举例:

int main(){
    printf("welcome to TU Berlin");//输出缓冲区
    sleep(2);
    _exit(108);//干掉进程,不会对缓冲区数据做任何刷新

    return 0;
}

image-20230723230755890

图10 _exit()函数调用调试结果
exit(int code){
	//冲刷缓冲区
	_exit(code);
}

这里我推荐使用exit()函数,上述代码可知exit()函数是_exit()的封装,但_exit()函数退出时没有刷新缓冲区。那么缓冲区在哪里?这个缓冲区不在操作系统内部,而是在C库。

异常退出

  • ctrl + c,信号终止

image-20230608235516607

图11 进程的执行
./mytest  //mytest变成一个子进程,父进程是bash

三、进程等待

3.1 进程等待必要性

  • 子进程退出,若父进程不管不顾就会造成“僵尸进程”,从而引起内存泄漏
  • 另外,进程一旦变成僵尸进程,那就刀枪不入,kill -9也无能为力,因为谁也无法杀死一个已死的进程;
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如子进程运行完成,结果正确还是错误,亦或是否正常退出;
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

image-20230723233631825

图12 进程资源等待

等待本质就是通过系统调用,获取子进程退出码或退出信号的方式。

3.2 进程等待的方法

3.2.1 wait方法

#include<sys/types.h>

#include<sys/wait.h>

pid_t wait(int *status);

返回值:成功返回被等待进程pid,失败则返回-1.

参数:输出型参数,获取子进程退出状态,不关心则可设置成NULL.

3.2.2 waitpid方法

image-20230723235826388

图13 waitpid()函数

返回值:

  • 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子集可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。

注意:status是输出型参数。

参数:

pid:

  • pid=-1,等待任一个子进程,与wait等效;

  • pid>0,等待其进程ID与pid相等的子进程。

status:

  • WIFEXITED(status):若为正常终止子进程返回的状态,则为真

  • WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码

options:

  • WNOHANG:若pid指定的子进程没有结束,则waitpid()返回0,不予以等待;若正常结束,则返回该子进程的ID;

若子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并释放资源,获得子进程退出信息;

若在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞;

若不存在该子进程,则立即出错返回。

image-20230802125556238

图14 父进程阻塞,在wait子进程退出后继续

3.2.3 获取子进程status

  • wait和waitpid,都是一个status参数,该参数是一个输出型参数,由操作系统填充;
  • 若传递NULL,则表示不关心子进程的退出状态信息;
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程;
  • status不能简单的当作整型来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位)

image-20230724001354882

图15 退出码与退出信号

  图15中,0表示没有收到信号,正常退出 -> 退出码(0 1 2…)。下列的实例1为wait方法的使用:

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        int cnt = 5;
        while(cnt){
            printf("我是子进程, 我还活着呢, 我还有%ds, PID:%d, PPID: %d\n",cnt--, getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    //父进程
    pid_t ret_id = wait(NULL);
    printf("我是父进程, 等待子进程成功, PID: %d, PPID: %d, ret_id: %d\n",getpid(), getppid(), ret_id);
    sleep(5);
}

image-20230724003338528

图16 wait()函数的使用

实例2为退出码与退出信号的simulate:

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        int cnt = 5;
        while(cnt){
            printf("我是子进程, 我还活着呢, 我还有%ds, PID:%d, PPID: %d\n",cnt--, getpid(), getppid());
            sleep(1);
        }
        exit(0);
    }
    sleep(10);
    //父进程
    int status = 0;
    pid_t ret_id = waitpid(id, &status, 0);
    printf("我是父进程, 等待子进程成功, PID: %d, PPID: %d, ret_id: %d, child exit status: %d, child exit signal: %d\n",\
    getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);
    sleep(5);
    // return 0;
}

image-20230724003144010

图17 waitppid()函数使用

父进程在wait的时候,若子进程没有退出,父进程只能一直在调用waitpid进行等待(阻塞等待)。

四、进程程序替换

4.1 替换原理

  用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行,调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

image-20230802132202121

图18 进程替换原理

4.2 替换函数

将以exec开头的函数,统称为exec函数:

#include<unistd.h>
int execl(const char* path, const char* arg, …);
int execv(const char* path, char* const argv[]);

int execlp(const char* file, const char* arg, …);
int execvp(const char* file, char* const argv[]);

int execle(const char* path, const char* arg, …, char *const envp[]);

int execve(const char* path, char* const argv[], char* const envp[]);

image-20230802173401488

图19 exec函数

4.2.1 函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回;
  • 若果调用出错,则返回-1;
  • 所以exec函数只有出错的返回值,而没有成功的返回值。

4.2.2 命名理解

这些函数原型看起来很容易混,若掌握规律则很好记忆:

  • l(list): 表示参数采用列表
  • v(vector): 参数用数组
  • p(path): 有p自动搜索环境变量PATH
  • e(env): 表示自己维护环境变量
函数名参数格式是否带路径是否使用当前环境变量
execl列表NO
execlp列表YES
execle列表NO不是,须自己组装env
execv数组NO
execvp数组YES
execve数组NO不是,须自己组装env

4.3 替换函数案例

4.3.1 execl调用举例

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id ==0){
        printf("我是子程序, pid: %d, ppid: %d\n", getpid(), getppid());
        execl("/bin/ls", "ls", "-a", "-l", NULL);//必须NULL结束
        //执行程序替换,新的代码和数据就被加载了,后续的代码属于旧代码,直接被替换,没有机会执行了
        printf("子程序还在呢!\n");
    }
    sleep(5);
    while(1){
        int status = 0;
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        if(ret_id < 0){
            printf("waitpid error!\n");
            exit(0);
        }else if(ret_id == 0){
            printf("子程序还活着呢!\n");
            continue;
        }else{
            printf("我是父进程, 我的pid: %d, child exit code: %d, child exit signal: %d\n",\
                getpid(), (status>>8)&0xFF, status & 0x7F);
            break;
        }
    }
}

image-20230801113324673

图20 execl替换函数

注意execl,如果替换成功,不会有返回值;如果替换失败,一定返回值 。

4.3.2 execv调用举例

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id == 0){

        printf("我是子程序, pid: %d, ppid: %d\n", getpid(), getppid());
        char* const myargv[] = {
            "ls",
            "-a",
            "-n",
            NULL
        };
        execv("/bin/ls", myargv);//命令ls -an替换子进程
        printf("you failed!\n");
    }
    sleep(5);
    while(1){
        int status = 0;
        pid_t ret_id = waitpid(id, &status, WNOHANG);
        if(ret_id < 0){
            printf("waitpid error!\n");
            exit(0);
        }else if(ret_id == 0){
            printf("子程序还活着呢!\n");
            continue;
        }else{
            printf("我是父进程, 我的pid: %d, child exit code: %d, child exit signal: %d\n",\
                getpid(), (status>>8)&0xFF, status & 0x7F);
            break;
        }
    }
}

image-20230801114156242

图21 execv替换函数

4.3.3 execlp调用举例

int execlp(const char* file, const char* arg,…);

p表示path(也是相对路径),当我们执行指定程序的时候,只需要指定程序名即可,系统会自动在环境变量PATH中进行查找。

int main(){
    pid_t id = fork();
    assert(id >= 0);
    (void)id;

    if(id == 0){
        //child
        printf("我是子进程, PID: %d\n", getpid());
        execlp("ls", "ls", "-a", "-i", NULL);//命令ls -ai来替换子进程
        printf("you fail\n");
    }
    //father
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
}

image-20230801114824467

图22 execlp替换函数

4.3.4 execvp调用举例

int execvp(const char* file, char* const argv[]);

execvp中p就不需要绝对路径

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        printf("我是子进程, PID: %d\n", getpid());
        char* const myargv[] = {
            "ls",
            "-a",
            "-l",
            "-n",
            NULL
        };
        execvp("ls",myargv);
        printf("you failed\n");
    }
    //父进程
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
    return 0;
}

image-20230801115044943

图23 execvp替换函数

4.3.5 execl替换的单个子进程

int main(){
    pid_t id = fork();
    if(id == 0){
        //子进程
        printf("我是子进程, PID: %d\n", getpid());
        execl("./exec/otherproc","otherproc",NULL);
        printf("you failed\n");
    }
    //父进程
    sleep(5);
    int status = 0;
    printf("我是父进程, PID: %d\n", getpid());
    waitpid(id, &status, 0);
    printf("child exit code: %d\n", WEXITSTATUS(status));
    return 0;
}

image-20230801115618992

图24 execl替换单个子进程

五、进程控制项目实战——模拟shell

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

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr, char* argv[]){
    assert(commandstr);
    assert(argv);

    argv[0] = strtok(commandstr, SEP);
    if(argv[0] == NULL){
        return -1;
    }

    int i = 1;
    while(1){
        argv[i] = strtok(NULL,SEP);
        if(argv[i] == NULL){
            break;
        }
        i++;
    }
    return 0;
}

void debugPrint(char* argv[]){
    for(int i = 0; argv[i]; i++){
        printf("[%d]: %s\n", i, argv[i]);
    }
}

void showEnv(){
    extern char** environ;
    for(int i = 0; environ[i]; i++){
        printf("%d: %s\n", i, environ[i]);
    }
}

int main(){
    int last_exit = 0;

    char myenv[32][256];
    int env_index = 0;
    while(1){
        char commandstr[MAX] = {0};
        char* argv[ARGC] = {NULL};
        printf("[ChuHsiang@HuaWeiCloud]# ");
        fflush(stdout);
        // sleep(100);

        char* s = fgets(commandstr, sizeof(commandstr), stdin);
        assert(s);
        (void)s;

        commandstr[strlen(commandstr) - 1] = '\0';

        int n = split(commandstr,argv);
        if(n != 0){
            continue;
        }
        // debugPrint(argv);
        //version 2 : 说明几个细节

        //cd .. /cd/ 等 bash自己执行的命令,称之为内建命令/内置命令  --> int chdir(const char *path);
        if(strcmp(argv[0], "cd") == 0){
            if(argv[1] != NULL){
                //说到底,cd命令,重要的表现就如同bash自己调用了对应的函数
                chdir(argv[1]);
            }
            continue;//不会往下继续执行,回到while(1)重新开始
        }else if(strcmp(argv[0], "export") == 0){
            if(argv[1] != NULL){
                strcpy(myenv[env_index], argv[1]);
                putenv(myenv[env_index++]);
            }
            continue;
        }else if(strcmp(argv[0], "env") == 0){
            showEnv();
            continue;
        }else if(strcmp(argv[0], "echo") == 0){
            //echo $PATH
            const char* target_env = NULL;
            if(argv[1][0] == '$'){
                if(argv[1][1] == '?'){
                    printf("%d\n", last_exit);
                    continue;
                }else{
                    target_env = getenv(argv[1]+1);
                }
            }
            if(target_env != NULL){
                printf("%s=%s\n", argv[1]+1, target_env);
            }

            continue;
        }

        // else if(strcmp(argv[0], "export") == 0){
        //     if(argv[1] != NULL){
        //         putenv(argv[1]);
        //     }
        //     continue;
        // }

        if(strcmp(argv[0], "ls") == 0){
            int pos = 0;
            while(argv[pos]){
                pos++;
            }
            argv[pos++] = (char*)"--color=auto";
            argv[pos] = NULL;//比较安全的做法
        }

        //version 1
        pid_t id = fork();
        assert(id >= 0);
        (void)id;

        if(id == 0){
            //child
            // execvp(argv[0], argv);
            execvp(argv[0], argv);
            exit(1);
        }
        int status = 0;
        pid_t ret_id = waitpid(id, &status, 0);
        if(ret_id > 0){
            last_exit = WEXITSTATUS(status);
        }
    }
}

image-20230802174445975

图25 simulate of shell

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

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

相关文章

英语不好能学好Python吗?Python常用英文单词汇总

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 有些小可爱对英语好不好对学习python有没有什么影响有着很深的疑惑。 其实python学习&#xff0c;主要靠多敲多练&#xff0c;主打一个熟能生巧 那今天我就给大家带来Python常用英文单词汇总&#xff0c; 新手期小可…

关于领导要求logback日志时间格式要求为“年-月-日 时:分:秒,毫秒”

今天接到领导邮件要求整改系统输出日志规范&#xff0c;有一条要求调整输出日志时间格式为标题所述格式&#xff0c;例&#xff1a;2022-02-21 14:13:32,489 项目目前logback.xml里的配置是这样&#xff1a; <pattern>%d{yyyyMMdd hh:mm:ss} [%p][%c][%M][%L]-> %m%…

docker容器所有操作、创建私有仓库

目录 一、创建容器与运行容器 1、创建容器 2、启动容器&#xff08;后面加镜像:标签或者唯一ID都可以&#xff09; 3、容器的运行与终止 4、容器的进入 5、容器的导出与导入 6、容器的删除 7、文件复制 8、查看容器资源使用率 9、查看容器进程状态 10、更新容器配置 …

捷码低代码|5种常见的布局组件介绍!

在捷码中亲自体验&#xff0c;学习效果更好&#xff01;没有账号的&#xff0c;点击免费获得账号&#xff1a;http://dev.gemcoder.com/front/development/index.html#/login 本文会介绍五种布局组件&#xff0c;即流式布局、弹性布局、固定布局、多行多列布局、Layout布局。 一…

喜讯! WorkPlus入选中国信通院数字产品“2023全景图”!

“2023数字生态发展大会”暨中国信通院“铸基计划” WorkPlus喜讯 7月27日&#xff0c;中国信息通信研究院&#xff08;下称“中国信通院”&#xff09;主办的“2023数字生态发展大会”暨中国信通院“铸基计划”年中会议在京召开&#xff0c;大会全面地总结了“铸基计划”上半…

企业优化效率,进行数据在线管理是不二选择

如今商业环境的竞争是愈发激烈了起来&#xff0c;企业优化效率成为了提高竞争力和发展壮大的关键。数据与图文档管理是企业运营中不可或缺的一环&#xff0c;传统管理方式已不适应快速变化的市场需求和多样化的业务流程。而在线图文档管理结合BOM系统作为企业数字化管理的不二选…

无涯教程-Lua - repeat...until 语句函数

与 for 和 while 循环(它们在循环顶部测试循环条件)不同&#xff0c;Lua编程中的 repeat ... until 循环语言在循环的底部检查其条件。 repeat ... until 循环与while循环相似&#xff0c;不同之处在于&#xff0c;保证do ... while循环至少执行一次。 repeat...until loop - …

openGauss学习笔记-28 openGauss 高级数据管理-NULL值

文章目录 openGauss学习笔记-28 openGauss 高级数据管理-NULL值28.1 IS NOT NULL28.2 IS NULL openGauss学习笔记-28 openGauss 高级数据管理-NULL值 NULL值代表未知数据。无法比较NULL和0&#xff0c;因为它们是不等价的。 创建表时&#xff0c;可以指定列可以存放或者不能存…

AI绘画| 迪士尼风格|可爱头像【附Midjourney提示词】

Midjourney案例分享 图片预览 迪士尼风格&#xff5c;可爱头像 高清原图及关键词Prompt已经放在文末网盘&#xff0c;需要的自取 在数字艺术的新时代&#xff0c;人工智能绘画已经迅速崭露头角。作为最先进的技术之一&#xff0c;AI绘画结合了艺术和科学&#xff0c;开启了一…

WPF上位机6——文件操作、多线程

文件操作 文件夹操作 创建文件夹 磁盘信息 文件的读写 文件流 Thread多线程 带参数创建线程 Task多线程 创建方式1 第一种 第二种 第三种&#xff1a;线程池的方式 前台与后台线程

开源SCRM营销平台MarketGo-客户群裂变

一、概述 随着互联网社交的普及&#xff0c;企业通过社交渠道进行营销已经成为一种不可或缺的营销手段。在企微SCRM社交媒体营销模式中&#xff0c;基于留存用户做快速的拉取新客户的方式&#xff0c;叫裂变活动&#xff0c;下面我们就基于客户群裂变的概念、特点和方法做简单…

基于C#制作一个串口通信调试软件

串口调试软件是一种用于调试和监测串口通信的工具软件。它可以帮助用户通过串口与外部设备进行通信,并实时显示发送和接收的数据,方便用户进行数据的分析和调试。 串口知识了解什么是串口通信原理波特率数据位停止位奇偶校验位RS-232标准合格的通信软件应具备的特点实现步骤界…

css实现文字颜色渐变+阴影

效果 代码 <div class"top"><div class"top-text" text"总经理驾驶舱">总经理驾驶舱</div> </div><style lang"scss" scoped>.top{width: 100%;text-align: center;height: 80px;line-height: 80px;fo…

【Linux指令篇】--- Linux常用指令汇总(克服指令繁杂问题)

文章目录 前言&#x1f31f;一、Linux基本指令&#x1f31f;二、ls指令&#x1f30f;2.1.语法&#xff1a;&#x1f30f;2.2.功能&#xff1a;&#x1f30f;2.3.常用选项&#xff1a; &#x1f31f;三、pwd指令&#x1f30f;3.1.语法&#xff1a;&#x1f30f;3.2.功能&#xf…

软件设计师(五)软件工程基础知识

一、软件工程概述 软件开发和维护过程中所遇到的各种问题称为“软件危机”。 软件工程是指应用计算机科学、数学及管理科学等原理&#xff0c;以工程化的原则和方法来解决软件问题的工程&#xff0c;其目的是提高软件生产率、提高软件质量、降低软件成本。 #mermaid-svg-h3j6K…

誉天程序员-瀑布模型-敏捷开发模型-DevOps模型比较

文章目录 2. 项目开发-开发方式2.1. 瀑布开发模型2.2. 敏捷开发模型2.3. DevOps开发模型2.4. 区别 自增主键策略1、数据库支持主键自增自增和uuid方案优缺点 2. 项目开发-开发方式 由传统的瀑布开发模型、敏捷开发模型&#xff0c;一跃升级到DevOps开发运维一体化开发模型。 …

天津web前端开发培训班 零基础如何学习前端?

学习Web前端有很多好处&#xff0c;它可以提高你的数字技能&#xff0c;使你更具有竞争力&#xff0c;而且Web前端是一个需求量很大的岗位&#xff0c;有这项技能在手&#xff0c;你可以轻松地找到一份工作。 什么是web前端 前端开发是创建web页面或app等前端界面给用户的过程…

隐私计算互联互通第二批试点项目及标准解读

为进一步促进数据高效流通和数据要素市场高质量发展&#xff0c;推动隐私计算产业健康快速发展。2023隐私计算大会暨首届“星河杯”隐私计算大赛颁奖典礼活动于7月26日在青岛成功举办&#xff0c;吸引了过万人次关注。 DataFountain大数据竞赛平台&#xff08;简称DF平台&…

一款超强的终端复用神器 --Tmux介绍与键位配置(超详细)

Halo&#xff0c;这里是Ppeua。平时主要更新C&#xff0c;数据结构算法&#xff0c;Linux与ROS…感兴趣就关注我bua&#xff01; 文章目录 1.什么是Tmux&#xff1f;session、windows、pane 2.使用Tmux2.1安装Tmux2.2 创建第一个Tmux窗口SessionWindowPane 3.Tmux配置设置为vi…

解释器模式——自定义语言的实现

1、简介 1.1、文法规则和抽象语法树 解释器模式描述了如何为简单的语言定义一个文法&#xff0c;如何在该语言中表示一个句子&#xff0c;以及如何解释这些句子。在正式分析解释器模式结构之前&#xff0c;先来学习如何表示一个语言的文法规则以及如何构造一棵抽象语法树。 …