【Linux】Linux的进程控制

目录

1. 学习思维导图

2.进程创建(fork)

2.1 fork创建进程失败

3.进程终止

3.1 进程退出情况

3.1.1main函数

3.1.2 退出码

3.2 exit/_exit函数

1. exit() 函数

2. _exit() 函数

4.进程等待

4.1 实现进程等待的方法

wait/waitpid方法

区别:

4.2 status参数

5.进程替换

5.1 认识exec*系列函数

6.自定义Shell


1. 学习思维导图

2.进程创建(fork)

  • 目标:理解进程创建的原理及fork函数的使用。

步骤

  创建成功会返回给父进程子进程的PID, 返回给子进程0,创建失败返回给父进-1。

  实例代码:

  • 阅读文档中fork函数的定义和返回值逻辑。
  • 理解父子进程的执行流程及输出结果
  •   实例代码:

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // 子进程
        printf("我是一个子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());
    }
    else if(id >= 1)
    {
        // 父进程
        printf("我是一个父进程,我的pid:%d\n", getpid());
    }
    return 0;
}
[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_9]$ ./process 
我是一个父进程,我的pid:1297
我是一个子进程,我的pid:1298,ppid:1297

  三个问题:

  • 为什么fork()函数有多个返回值?

  父进程创建子进程后,子进程的PCB指向的是父进程的代码和数据,由于进程是独立的,所以fork()函数被调用了两次(父和子),因此返回值有多个!

  • 为什么返回给父进程子进程的pid,而返回子进程0

  因为子进程归父进程管理,为了方便管理和找到子进程所以父进程会被返回子进程的pid。

  子进程 : 父进程 ----> n : 1 ,一个父进程可以拥有多个子进程

  • 为什么id可以接受多个返回值,并且使得if语句同时成立?

  我们先打印父子进程的变量id地址观察:

我是一个父进程,我的pid:2198
id:0x7ffd0f3e500c
我是一个子进程,我的pid:2199,ppid:2198
id:0x7ffd0f3e500c

  我们会发现地址是一样的,那么id的多值行为怎么解释呢? ---> 写时拷贝,而计算机给我们的id地址是一份虚拟地址。

2.1 fork创建进程失败

  • 创建进程数超过最大数

  • 空间资源不够创建进程

3.进程终止

3.1 进程退出情况

  • 代码运行完毕,结果正确

  • 代码运行完毕,结果不正确

  • 代码异常终止

3.1.1main函数

  • main函数我们常常写成这样:

int main()
{
    return 0;
}
  1. main函数的返回值返回给谁了?

  2. 为什么通常返回0,有什么特殊含义吗?

main函数作为程序的入口函数,通常代表一个程序的结果,如果一个程序正常运行完毕,结果正确返回值为0,如果结果错误返回值为!0,如果代码程序异常终止那么返回值无意义!返回值通常存储在寄存器中进行返回!

  • 返回结果正确返回值为0不用管。

  • 返回值不正确非0,不同的值代表不同的问题!

案例:打开一个不存在的文件:

查看退出码(返回值):

命令: echo $?    #打印最近一个程序的退出码

[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_11]$ ./process 
[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_11]$ echo $?
1
# Bash进程会收集子进程process的退出码
# 这个退出码会写进进程的task_struct(PCB)

查看所有错误码的信息:

int main()
{
    for(int i = 0; i < 150; ++i)
        printf("strerror[%d]->%s\n", i, strerror[i]);
    return 0;
}
[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_11]$ ./process 
strerror[0]->Success
strerror[1]->Operation not permitted
strerror[2]->No such file or directory
strerror[3]->No such process
strerror[4]->Interrupted system call
strerror[5]->Input/output error
strerror[6]->No such device or address
strerror[7]->Argument list too long
strerror[8]->Exec format error
strerror[9]->Bad file descriptor
strerror[10]->No child processes
strerror[11]->Resource temporarily unavailable
strerror[12]->Cannot allocate memory
strerror[13]->Permission denied
strerror[14]->Bad address
strerror[15]->Block device required
strerror[16]->Device or resource busy
strerror[17]->File exists
strerror[18]->Invalid cross-device link
strerror[19]->No such device
strerror[20]->Not a directory
strerror[21]->Is a directory
........................................................

返回一个错误码,直接使用return errno;


[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_11]$ ./process 
[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_11]$ echo $?
2
# strerror[1]->Operation not permitted

3.1.2 退出码

退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。代码 1 或 0 以外的任何代码都被视为不成功。

Linux Shell中的主要退出码:

3.2 exit/_exit函数

1. exit() 函数

  • 定义:属于C标准库(stdlib.h),是高层级的进程终止函数。

  • 行为

    • 执行清理操作:刷新所有标准I/O缓冲区(如printf的输出),关闭已打开的文件流。

    • 调用通过 atexit() 注册的退出处理函数。

    • 最后调用 _exit() 终止进程,向操作系统传递退出状态。

  • 使用场景:通常用于正常终止程序,确保资源正确释放。

#include <stdlib.h>
int main() {
    printf("Hello");  // 无换行,但exit会刷新缓冲区
    exit(0);          // 输出"Hello"
}

2. _exit() 函数

  • 定义:属于系统调用(unistd.h),是低层级的进程终止函数。

  • 行为

    • 直接终止进程,不刷新I/O缓冲区,不调用退出处理函数。

    • 立即将控制权交还给操作系统。

  • 使用场景:多用于子进程(如fork()后),避免干扰父进程的I/O状态。

示例

#include <unistd.h>
int main() {
    printf("Hello");  // 无换行
    _exit(0);         // 直接终止,不输出"Hello"
}

典型应用场景

  • 子进程终止:在fork()后的子进程中,若直接调用exit()可能导致父进程的I/O缓冲区被重复刷新(如输出两次)。此时应使用_exit()

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

int main() {
    if (fork() == 0) {   // 子进程
        printf("Child");
        _exit(0);        // 避免刷新父进程的缓冲区
    } else {             // 父进程
        wait(NULL);
        printf("Parent");
        exit(0);
    }
}
  • 立即终止:当程序遇到致命错误且无需清理时(如exec失败后的子进程)。


总结

  • 需要资源清理(如保存数据)时用 exit()

  • 需要立即终止(如避免缓冲区冲突)时用 _exit()

  • 库函数exit()是对_exit()的封装! --> 通过这两个调用我们发现一件事情关于我们谈论的缓冲区的位置,我们知道一定不是系统缓冲区之中,我们使用的是库提供的缓冲区!

4.进程等待

前言:我们之前了解了僵尸进程,而进程等待就是为了解决僵尸进程的问题的!

进程等待的必要性:

  • 子进程接受父进程的任务安排,是否完成任务都会有一个返回值,父进程接受这个返回值的过程可以称作为进程等待

  • 进程等待是父进程的一种手段,是为了实现对子进程的资源回收(最主要的),和对子进程的进程退出信息的捕获(可以选择的,我们知道一个函数不是必须要有一个返回值,这边也是这种意思)。

4.1 实现进程等待的方法

wait/waitpid方法
WAIT(2)                                                         Linux Programmer's Manual                                                         WAIT(2)

NAME
       wait, waitpid, waitid - wait for process to change state

SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *status);

       pid_t waitpid(pid_t pid, int *status, int options);
  1. wait方法

  • 作用:会阻塞父进程(只有接收到子进程退出信息父进程后面的代码才会执行),直到任意一个子进程退出。status参数是用于获取子进程的退出状态的。

  • 返回值:成功返回被获取的子进程的PID,失败返回-1。

  1. waitpid方法

  • 作用:相较于wait方法waitpid方法更加精准高效

    • PID > 0: 它可以设置传入参数pid用于指定等待哪个子进程

    • pid = -1:等待任意子进程(类似 wait)。

    • pid = 0:等待与父进程同进程组的所有子进程。

    • pid < -1:等待进程组 ID 为 |pid| 的所有子进程。

  • options 参数(常用选项)

    • 0:默认阻塞模式。

    • WNOHANG:非阻塞模式,立即返回。若无子进程退出,返回 0

  • 返回值

    • 成功:返回被回收的子进程 PID。

    • 失败:返回 -1(如无子进程)。

    • WNOHANG 且无退出子进程waitpid 返回 0

区别:

4.2 status参数

这个参数由子进程的退出码和退出信号决定,如果在该参数处传入NULL/nullptr,表示不关心子进程退出状态信息,这个status参数由OS输出填充。

我们要通过status获取退出码可以使用位运算:

[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_13]$ ./process 
我是一个子进程!我是父进程,我接收到退出码:8

或者可以使用宏解析:

  • WIFEXITED(status):子进程是否正常退出。

  • WEXITSTATUS(status):获取子进程退出码。

  • WIFSIGNALED(status):子进程是否被信号终止。

  • WTERMSIG(status):获取终止子进程的信号。

5.进程替换

  • 什么是进程替换:将一个进程的代码和数据替换成另一个进程!

  • 进程替换一旦成功,后续就执行新的代码和数据。

    RETURN VALUE
           The  exec()  functions return only if an error has occurred.  The return value is -1, and errno is set to
           indicate the error.

  • 进程替换不会生成新的进程只是将老进程的代码和数据覆盖式的替换了。

  • 进程替换使用的是exec*系列函数,这类函数无需关系返回值,因为一旦返回代表失败!

进程替换案例:

[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_13]$ ./process 
代码程序开始了!
total 24
drwxrwxr-x  2 ouyang ouyang 4096 Feb 13 14:45 .
drwxrwxr-x 14 ouyang ouyang 4096 Feb 13 13:56 ..
-rwxrwxr-x  1 ouyang ouyang 8496 Feb 13 14:45 process
-rw-rw-r--  1 ouyang ouyang  824 Feb 13 14:45 process.c

5.1 认识exec*系列函数

  • l(list):表示参数采用列表

  • v(vector):参数用数组

  • p(path):有p自动搜索环境变量PATH

  • e(env):表示自己维护环境变量

  • 传参方面:1. 路径+程序名 2. 命令怎么写就怎么输入

// 库函数
#include <unistd.h>
extern char **environ; // 全局环境变量指针

// 需要传入程序的具体地址,执行参数,NULL表示参数结尾
int execl(const char *path, const char *arg, ...);
// 只需要传入程序名字,根据环境变量查找
int execlp(const char *file, const char *arg, ...);
// 需要传入程序的具体地址,环境变量可以不使用默认全局的,可以自己配置
int execle(const char *path, const char *arg, ..., 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[]);
// 系统调用
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);

库函数是对系统调用进行的函数封装!

  • execl

int main()
{
    printf("Start\n");
    execl("/bin/ls", "-a", "-l", NULL);   // NULL表示传参结束
    printf("End\n");
    return 0;
}
[ouyang@iZ2ze0j6dd76e0o9qypo2rZ dir_2025_2_13]$ ./process 
Start
total 16
-rwxrwxr-x 1 ouyang ouyang 8496 Feb 13 14:56 process
-rw-rw-r-- 1 ouyang ouyang  984 Feb 13 14:56 process.c
  • execlp(提供环境变量)

int main()
{
    printf("Start\n");
    execlp("ls", "-a", "-l", NULL);   // NULL表示传参结束
    printf("End\n");
    return 0;
}
  • execv (自己提供argv[])

char* const argv[]{
    "ls",
    "-a",
    "-l",
    NULL
};

int main()
{
    printf("Start\n");
    execv("ls", argv);   // NULL表示传参结束
    printf("End\n");
    return 0;
}
  • 如果需要在原本的基础上新增环境变量使用putevn() 环境变量

SYNOPSIS
#include <stdlib.h>
int putenv(char *string);

6.自定义Shell

设计图:重定向方面的实现(参考I/O操作)

      
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

// 基本信息PutName大小
#define PUTSIZE 1024
// 获取命令行变量Command大小
#define COMMANDSIZE 1024
// 打印格式
#define PUTFOEM "[%s@%s %s] # "

// 我们的环境变量表
#define MAX_ENVS 120
char* g_env[MAX_ENVS];
int g_envs = 0;

// 我们的命令行参数表
#define MAX_ARGV 128
char* g_argv[MAX_ARGV];
int g_argc = 0;

// 实时获取cwd
char cwd[1024];
// 更新env的cwd
char cwdenv[1024];

// 上一次的退出码
int lastcode = 0;

// 4. 关于重定向,我们关心的内容
#define NONE_REDIR 0        // 无
#define INPUT_REDIR 1       // <
#define OUTPUT_REDIR 2      // >
#define APPEND_REDIR 3      // >>
int redir = NONE_REDIR;
std::string filename;

/* 获取父进程的环境变量表 */
void Init_Env()
{
    extern char**  environ; // 全局环境变量表
    memset(g_env, 0, sizeof(g_env)); // 初始化我们的环境变量表
    g_envs = 0;

    /* 获取父进程环境变量表的内容 */
    for(int i = 0; environ[i]; i++)
    {
        // 1. 申请空间
        g_env[i] = (char*)malloc(strlen(environ[i]) + 1);
        // 2. 拷贝内容
        strcpy(g_env[i], environ[i]);
        g_envs++;
    }
    // 标志
    g_env[g_envs++] = (char*)"MyShell = Ouyang Test";
    // 最后结尾以NULL
    g_env[g_envs] = NULL;
    
    /* 导入环境变量 */
    for(int i = 0; g_env[i]; ++i)
        putenv(g_env[i]);
    // 更改指向
    environ = g_env;
}

/* 测试环境变量是否导入成功 */
void Print_env()
{
    extern char** environ;
    for(int i = 0; environ[i]; ++i)
        printf("%d->%s\n", i, environ[i]);
}

/* 用户名 / 主机名 / 绝对路径 / 家目录 */
const char* Get_User()
{
    const char* GU = getenv("USER");
    return GU == NULL ? "None" : GU;
}

const char* Get_Hostname()
{
    const char* GH = getenv("HOSTNAME");
    return GH == NULL ? "None" : GH;
}

const char* Get_PWD()
{
    // const char* GP = getenv("PWD");
    const char* GP = getcwd(cwd, sizeof(cwd));
    if(GP != NULL)
    {
        snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
        putenv(cwdenv);
    }
    return GP == NULL ? "None" : GP;
}

const char* Get_Home()
{
     const char* Home = getenv("HOME"); 
    return Home == NULL ? "None" : Home;
}

const char* Get_Oldpwd()
{
    const char* old_pwd = getenv("OLDPWD");
    return old_pwd == NULL ? "None" : old_pwd;
}

/* 获取相对路径 */
std::string DirName(const char* pwd)
{
#define SLASHE "/"
    std::string rp = pwd;
    if(rp == SLASHE) return SLASHE;
    /* 找到最后的"/"位置 */
    auto pos = rp.rfind(SLASHE);
    if(pos == std::string::npos) return "Get P_Path Fail!";
    return rp.substr(pos + 1);
}

/* 基本信息的打印 */
void PutBaseInformation()
{
    fflush(stdout);
    char PutName[PUTSIZE];
    const char* pwd = Get_PWD();
    snprintf(PutName, sizeof(PutName), PUTFOEM, Get_User(), Get_Hostname(), DirName(pwd).c_str());
    printf("%s", PutName);
    fflush(stdout);
}

/* 获取命令行 */
bool GetCommandLine(char* out, int size)
{
    // "ls -a -l \n" -> "ls -a -l"
    if(fgets(out, size, stdin) == nullptr) return false;
    out[strlen(out) - 1] = 0; // 清理"/n"
    return strlen(out) > 0; // 没有命令输入不做处理
}


/* 解析命令 */
bool CommandPrase(char* out)
{
    // "ls -a -l" -> "ls" "-a" "-l"
    // 以" "为分隔符
#define SPACE " "
    g_argc = 0; // 重置
    g_argv[g_argc++] = strtok(out, SPACE);
    while((bool)(g_argv[g_argc++] = strtok(NULL, SPACE)));
    g_argc--; // 减去多余的++
    return g_argc > 0;
}

/* 打印测试g_argv */
void Print_Argv()
{
    for(int i = 0; g_argv[i]; ++i)
        printf("g_argv[%d]->%s", i, g_argv[i]);
}


/* Cd */
void Cd()
{
    // Cd / Cd - / Cd ~
    if(g_argc == 1)
    {
        // Cd
        std::string home = Get_Home();
        if(home.empty()) return;
        chdir(home.c_str());
    }
    else{
        // Cd - / Cd ~
        std::string op = g_argv[1];
        if(op == "~")
        {
            // 家目录
            chdir(Get_Home());
        }
        else if(op == "-")
        {
            // 之前目录
            chdir(Get_Oldpwd());
        }
        else{
            chdir(op.c_str());
        }
    }
}

/* Echo */
void Echo()
{
    if(g_argc == 2 && !(g_argc > 2))
    {
        // echo $?
        // echo "Hello"
        // echo $PATH
        std::string opt = g_argv[1];
        if(opt == "$?")
        {
            std::cout << lastcode << std::endl;
            lastcode = 0;
        }
        else if(opt[0] == '$')
        {
            std::string env_name = opt.substr(1);
            char* env = getenv(env_name.c_str());
            if(env)
                std::cout << env << std::endl;
        }
        else{
            std::cout << opt << std::endl;
        }
    }
}

/* 特殊处理内建命令 */
bool CheckAndExecBuiltin()
{
    std::string cmd = g_argv[0];
    if(cmd == "cd")
    {
        Cd();
        return true;
    }
    else if(cmd == "echo")
    {
        Echo();
        return true;
    }
    // ...
    return false;
}

void TrimSpace(char cmd[], int &end)
{
    while(isspace(cmd[end]))
    {
        end++;
    }
}

bool CheckRedirect()
{
    redir = NONE_REDIR;
    filename.clear();
    for (int i = 0; g_argv[i] != NULL; ++i)
    {
        if (strcmp(g_argv[i], "<") == 0)
        {
            redir = INPUT_REDIR;
            if (g_argv[i + 1] != NULL)
            {
                filename = g_argv[i + 1];
                g_argv[i] = NULL; // 截断参数列表
                return true;
            }
            return false;
        }
        else if (strcmp(g_argv[i], ">") == 0)
        {
            redir = OUTPUT_REDIR;
            if (g_argv[i + 1] != NULL)
            {
                filename = g_argv[i + 1];
                g_argv[i] = NULL; // 截断参数列表
                return true;
            }
            return false;
        }
        else if (strcmp(g_argv[i], ">>") == 0)
        {
            redir = APPEND_REDIR;
            if (g_argv[i + 1] != NULL)
            {
                filename = g_argv[i + 1];
                g_argv[i] = NULL; // 截断参数列表
                return true;
            }
            return false;
        }
    }
    return false;
}

/* 命令执行 */
int Execute()
{
    // 进程创建 + 进程替换
    pid_t id = fork();
    if(id == 0)
    {
        int fd = -1;
        // 子进程检测重定向情况
        if(redir == INPUT_REDIR)
        {
            fd = open(filename.c_str(), O_RDONLY);
            if(fd < 0) exit(1);
            dup2(fd, 0);
            close(fd);
        }
        else if(redir == OUTPUT_REDIR)
        { 
            umask(0);
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);
            if(fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        else if(redir == APPEND_REDIR)
        {
            umask(0);
            fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
            if(fd < 0) exit(2);
            dup2(fd, 1);
            close(fd);
        }
        // child
        execvp(g_argv[0], g_argv);
        exit(EXIT_FAILURE);
    }

    // father
    // 进程等待
    int st = 0;
    pid_t pid = waitpid(id, &st, 0);
    lastcode = WEXITSTATUS(st);
    return pid;
}


int main()
{
    /* 初始化我们的环境变量 */
      Init_Env();
   //  Print_env(); /* 测试Env */
    while(true)
    {
        /* 基本信息的打印 */
        PutBaseInformation();
        
        /* 命令行的获取 */
        char CommandLine[COMMANDSIZE];
        if(!GetCommandLine(CommandLine, sizeof(CommandLine)))
            continue;
        
        /* 解析命令 */
        if(!CommandPrase(CommandLine))
            continue; 
        
        /* 重定向分析 */
        if(CheckRedirect())
        {
            Execute();
            continue;
        }
        
        /*  特殊处理内建命令 */
        if(CheckAndExecBuiltin())
            continue;
        
        // 命令执行
        Execute();
    }
    return 0;
}

    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu防火墙iptables

文章目录 步骤开启自启防火墙iptables规则链Chains的区别 在 Ubuntu 上使用 iptables 配置防火墙并保证服务可用 步骤 #防火墙状态 systemctl status iptables systemctl start iptables #开启防火墙并且开启22端口 systemctl start iptables && iptables -A INPUT -p…

计算机毕业设计SpringBoot+Vue.js公司日常考勤系统(源码+文档+PPT+讲解)

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

企业微信里可以使用的企业内刊制作工具,FLBOOK

如何让员工及时了解公司动态、行业资讯、学习专业知识&#xff0c;并有效沉淀企业文化&#xff1f;一份高质量的企业内刊是不可或缺的。现在让我来教你该怎么制作企业内刊吧 1.登录与上传 访问FLBOOK官网&#xff0c;注册账号后上传排版好的文档 2.选择模板 FLBOOK提供了丰富的…

Hive-01之数仓、架构、数据类型、DDL、内外部表

一、主题 hive的核心概念hive与数据库的区别hive的架构原理hive的安装部署hive的交互式方式hive的数据类型hive的DDL语法操作 二、要点 1.数据仓库的基本概念 1.数据仓库的基本概念 英文名称为Data Warehouse&#xff0c;可简写为DW或DWH。数据仓库的目的是构建面向分析的…

【Markdown 语法简洁讲解】

Markdown 语法简洁语法讲解 什么是 Markdown1. 标题2. 列表3.文本样式4. 链接与图片5. 代码6. 表格7. 分割线8. 流程图9. 数学公式10. 快捷键11. 字体、字号与颜色 什么是 Markdown Markdown 是一种轻量级标记语言&#xff0c;通过简单的符号实现排版格式化&#xff0c;专注于…

数据如何安全“过桥”?分类分级与风险评估,守护数据流通安全

信息化高速发展&#xff0c;数据已成为企业的核心资产&#xff0c;驱动着业务决策、创新与市场竞争力。随着数据开发利用不断深入&#xff0c;常态化的数据流通不仅促进了信息的快速传递与共享&#xff0c;还能帮助企业快速响应市场变化&#xff0c;把握商业机遇&#xff0c;实…

Linux网络 TCP全连接队列与tcpdump抓包

TCP全连接队列 在 Linux 网络中&#xff0c;TCP 全连接队列&#xff08;也称为 Accept 队列&#xff09;是一个重要的概念&#xff0c;用于管理已经完成三次握手&#xff0c;即已经处于 established 状态但尚未被应用程序通过 accept( ) 函数处理的 TCP 连接&#xff0c;避免因…

网络流算法: Edmonds-Karp算法

图论相关帖子 基本概念图的表示: 邻接矩阵和邻接表图的遍历: 深度优先与广度优先拓扑排序图的最短路径:Dijkstra算法和Bellman-Ford算法最小生成树二分图多源最短路径强连通分量欧拉回路和汉密尔顿回路网络流算法: Edmonds-Karp算法网络流算法: Dinic算法 环境要求 本文所用…

Python的那些事第三十六篇:基于 Vega 和 Vega-Lite 的数据可视化解决方案,Altair 声明式可视化库

Altair 声明式可视化库:基于 Vega 和 Vega-Lite 的数据可视化解决方案 摘要 在数据科学和分析领域,有效的数据可视化是理解数据、发现模式和传达见解的关键。Python 作为数据科学的主要编程语言之一,提供了多种数据可视化库。其中,Altair 是一个基于 Vega 和 Vega-Lite 的…

文件描述符与重定向

1. open系统调用 在 Linux 中, open() 系统调用用于打开一个文件或设备&#xff0c;并返回一个文件描述符&#xff0c;通过该描述符可以进行文件读写操作。open() 可以用于创建新文件或打开已存在的文件&#xff0c;具体行为取决于传递给它的参数。 需要包含的头文件&#xf…

【WPF】绑定报错:双向绑定需要 Path 或 XPath

背景 最开始使用的是 TextBlock: <ItemsControl ItemsSource"{Binding CameraList}"><ItemsControl.ItemsPanel><ItemsPanelTemplate><StackPanel Orientation"Horizontal"/></ItemsPanelTemplate></ItemsControl.Item…

【Linux高级IO】五种IO模型 多路转接(select)

目录 1. 五种IO模型 1.1 阻塞式IO 1.2 非阻塞IO 1.3 信号驱动IO 1.4 多路转接 1.5 异步IO 2. 同步通信与异步通信 3. 多路转接 3.1 select 总结 1. 五种IO模型 1.1 阻塞式IO 阻塞式IO最为常见&#xff0c;在内核将数据准备好之前, 系统调用会一直等待&#xff0c;所有的…

Linux服务升级:Almalinux 升级 DeepSeek-R1

目录 一、实验 1.环境 2.Almalinux 部署 Ollama 3.Almalinux 升级 DeepSeek-R1 4.Almalinux 部署 docker 5. docker 部署 DeepSeek-R1 6.Almalinux 部署 Cpolar (内网穿透) 7.使用cpolar内网穿透 二、问题 1.构建容器失败 一、实验 1.环境 &#xff08;1&#xff09…

深度剖析数据分析职业成长阶梯

一、数据分析岗位剖析 目前&#xff0c;数据分析领域主要有以下几类岗位&#xff1a;业务数据分析师、商业数据分析师、数据运营、数据产品经理、数据工程师、数据科学家等&#xff0c;按照工作侧重点不同&#xff0c;本文将上述岗位分为偏业务和偏技术两大类&#xff0c;并对…

CosyVoice2整合包 特殊声音标记,声音克隆更逼真,新增批量生成

新增批量生成,可用于制作直播话术音频 特殊声音标记 符号示例1_语气加强<strong> </strong>每天都<strong>付出</strong>和<strong>精进</strong>&#xff0c;才能达到巅峰。2_呼吸声[breath][breath] 吸气,[breath] 呼气! [breath] 吸,[b…

vector习题

完数和盈数 题目 完数VS盈数_牛客题霸_牛客网 一个数如果恰好等于它的各因子(该数本身除外)之和&#xff0c;如&#xff1a;6321。则称其为“完数”&#xff1b;若因子之和大于该数&#xff0c;则称其为“盈数”。 求出2到60之间所有“完数”和“盈数”。 输入描述&#xff…

如何保证 Redis 缓存和数据库的一致性?

如何保证 Redis 缓存和数据库的一致性&#xff1f; 1. 问题出现场景 先修改数据库&#xff0c;再删除缓存 删除数据库数据成功了&#xff0c;但是删除缓存却失败了&#xff0c;缓存中仍保留的是旧数据 先删除缓存&#xff0c;再删除数据库 如果 Redis 缓存删除成功后&#xf…

信刻光盘安全隔离与信息交换系统让“数据摆渡”安全高效

随着数据传输、存储及信息技术的飞速发展&#xff0c;信息安全保护已成为重中之重。各安全领域对跨网数据交互的需求日益迫切&#xff0c;数据传输的安全可靠性成为不可忽视的关键。为满足业务需求并遵守保密规范&#xff0c;针对于涉及重要秘密信息&#xff0c;需做到安全的物…

网络原理--TCP/IP(2)

我们在之前已经介绍到TCP协议的核心机制二,接下来我们将继续介绍其他的核心机制。 核心机制三:连接管理 即建立连接,断开连接,在正常情况下,TCP要经过三次握⼿建⽴连接,四次挥⼿断开连接。 建立连接:TCP是通过“三次握手” 在生活中的握手就是打招呼,,但握手操作没有…

Windows PicPick Professional-v7.3.2-中文版

Windows PicPick Professional-中文版 链接&#xff1a;https://pan.xunlei.com/s/VOKGwGVGWUDl7L8cW4D1A1W4A1?pwdw5qz# - 更新了中文翻译&#xff0c;默认取消检测升级&#xff0c;删除多国语言