Linux/Uinx 系统编程:进程管理(1)

Linux/Uinx 系统编程:进程管理(1)

文章目录

  • Linux/Uinx 系统编程:进程管理(1)
    • 什么是进程
    • 进程来源
    • INIT 和 守护进程
    • 登录进程
    • sh进程
    • 进程的执行模式
    • 进程管理的系统调用
      • 关于syscall中参数b,c,d的作用
      • fork()
        • 进程执行顺序
      • nice()
      • sched_yield
    • 进程终止
      • 正常终止
        • wait() 等待子进程终止
      • 异常终止
      • 等待子进程终止

在学习编程内容之前先来学习一些基础知识:

  • 什么是进程
  • PROC结构体(PCB)是什么?
  • 什么是挂载?
  • 系统创建进程时都干了什么
  • 登录进程
  • sh进程
  • 进程执行模式

等等…

如果你不想学习和了解或者已经了解并熟悉了以上内容,你可以直接通过目录跳转到 :进程管理的系统调用

什么是进程

在操作系统里面,任务也被称作进程。

进程的正式定义:进程是对映像的执行

  • 程序:是静态的,就是存放在磁盘文件上的可执行文件如tim.exe
  • 进程:是动态的,是程序的一次执行过程,如:可以同时启动多次Tim程序

同一个程序可以执行多次进程

程序段、数据段、PCB三部分组成了进程实体进程映像)引入进程实体的概念后,可把进程定义为:
进程是进程实体的运行过程,是系统进行资源分配调度的一个独立单位。

一个进程被“调度”,就是指操作系统决定让这个进程上CPU被运行

程序段

  • 程序的代码(指令序列

数据段

  • 运行过程中产生的各种数据(如:程序中定义的变量)

PCB(PROC结构体)

PCB是进程存在的唯一标志

当进程被创建时,操作系统会为该进程分配一个唯一的、不重复的“身份证号”——PID (Process ID,进程ID)

操作系统要记录PID、进程所属用户ID (UID)
还要记录给进程分配了哪些资源(如:分配了多少内存、正在使用哪些I/0设备、正在使用哪些文件)
还要记录进程的运行情况(如:CPU使用时间、磁盘使用情况、网络流量使用情况等)

PCB也被称作PROC结构体

PROC结构体的内容大概是这样的:

typedef struct proc {
    struct proc *next;          // next proc pointer
    int *ksp;                   // saved stack pointer
    int pid;                    // pid = 0 to NPROC - 1
    int ppid;                   // parent pid
    int status;                 // PROC status
    int priority;                // scheduling priority
    int kstack[SSIZE];          // process stack
} PROC;

以上为最简单的格式,有时会根据具体需求再进行增加其他字段。

简单来说PCB中有以下的一些信息:

  • 进程描述信息
  • 进程控制和管理信息
  • 资源分配清单
  • 处理机相关信息

挂载

简单来说就是操作系统将一片物理的存储空间识别并调用(准确来说是挂接在一个已经存在的目录下,建议以此为准)的过程。

例如windows识别新的U盘/硬盘并且分配一个盘符D、E、F…

在linux中,没有windows中盘符的概念,只有根目录/,当插入新的硬盘时,我们无法从shell去访问这个块硬盘(新插入的硬盘在/dev/sdbx但是不可访问,你可能觉得这是一个目录,但是并不可以访问,sdbx是一个类似于指针的东西,指向的是新硬盘中的原始数据块,在没有挂载之前,系统并不知道如何使用这片区域),这个时候需要执行挂载指令:

mount /dev/sdb1 ~/newDIR

这个指令的作用是将新硬盘空间识别并将其作为一个目录放在~/下,目录名为newDIR


进程来源

当操作系统启动时,内核的启动代码通常会创建一个PID = 0 的初始进程, 通过分配PROC结构体(通常是PROC[0])进行创建。然后让指向运行进程的指针指向该结构体P0
P0中继续初始化系统,包括系统硬件和内核数据结构。
在此期间,他会挂载一个根文件系统,使得系统可以使用文件。在初始化系统之后,P0复刻出一个子进程P1,并且把进程切换为以用户模式运行的状态去运行P1

INIT 和 守护进程

当进程P1开始运行时,它将其执行映像更改为INIT程序

因此P1进程通常被称作INIT进程,目的是对当前系统环境进一步初始化。

具体做法是生成很多子进程,大部分子进程为系统服务,在后台运行,不与用户交互。

这种进程叫做守护进程

例如:

  • syslogd:log daemon process
  • inetd:Internet service daemon process
  • httpd:HTTP server daemon process
  • etc.

登录进程

除了守护进程之外,P1还复刻了许多LOGIN进程,每个终端上一个,用于用户登录。

每个LOGIN进程打开三个与自己的终端相关联的文件流,分别是:

  • stdin:用于标准输入
  • stdout:用于标准输出
  • stderr:用于错误信息输出

每个文件流都是指向进程堆区中FILE结构体的指针。每个FILE结构体记录一个文件描述符号(数字),stdin为0,stdout为1,stderr是2

然后每个LOGIN进程向stdout显示一个:

login:

等待用户登录。

用户账户保存在/etc/passwd/etc/shadow中。每个用户账户在表单的/etc/passwd文件中都有一行对应的记录:

name:x:gid:uid:description:home:program

其中:

  • name:用户名
  • x:密码
  • gid:用户组ID
  • uid:用户名ID
  • home:用户主目录
  • program:用户登录后执行的初始程序

用户的其他信息在:/etc/shadow文件中。

其中包括了加密的用户密码,可选的过期限制信息(如过期时期和时间等)。

sh进程

用户登录成功之后,LOGIN进程会获取用户的giduid,从而成为用户的进程。

它将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序sh。

这个sh进程就是我们通常所说的shell。

一些特殊命令(cd,退出,注销等)由sh自己执行,其他的大部分命令是存放在bin目录中的可执行文件(/bin, /sbin, /usr/bin, /usr/local/bin),对于这些命令,shell创建一个新的进程来执行这些命令,结束后返回到shell。

shell进程为父进程,执行的命令为子进程,子进程将执行映像更改为命令文件并执行命令程序。子进程在终止时会唤醒父进程sh,父进程会收集子进程的终止状态、释放子进程PROC结构体并提示执行另一个命令等

进程的执行模式

在 Linux/Uinx 中,进程以两种不同的模式执行:

  • 内核模式(Kmode)也叫内核态
  • 用户模式(Umode)也叫用户态

CPU中有一个状态寄存器,可以记录为K模式还是U模式。

在内核模式下,CPU通过修改状态寄存器就可以实现更改执行模式的状态,即从内核模式切换到用户模式。

但是在用户模式下是不能修改状态寄存器的,此时只能通过下面三种方式来进入内核态:

  • 中断:外部设备发送给CPU请求CPU服务的信号
  • 陷阱:错误条件,例如无效地址、非法指令、除以0等。在Linux/Uinx中,内核陷阱处理程序是将陷阱原因转换为信号编号,并将信号传递给进程。对于大多数信号,进程的默认操作是终止。
  • 系统调用:简称syscall,是一种允许用户模式下进程进入内核模式执行内核函数的机制。当某个进程执行完内核函数之后,它将期望结果和一个返回值返回给用户模式下的程序,该值通常为0或者-1,表示成功或者失败。如果发生错误,外部全局变量errno(包含在errno.h中)会包含一个ERROR代码,用于标识错误。用户可以使用库函数perror打印错误信息。

当进程进入内核态的时候,它可能不会立即返回到用户态。某些情况下甚至永远不会返回用户态,例如_exit()和大多数陷阱会导致程序在内核态中终止,当某个进程即将退出内核态时,系统可能会切换进程来允许一个具有更高优先级的进程。

进程管理的系统调用

主要有以下四个:

  • fork():创建子进程
  • wait():等待子进程
  • exec():更改进程执行映像
  • exit():终止退出

这四个函数的本质实际上都是调用了下面这个函数,只不过a的值不同而已。

int syscall(int a, int b, int c, int d);

a表示系统调用号,b、c、d表示对核函数的参数。在Intel x86系统的Linux中,系统调用是由汇编指令INT 0x80实现的,使得CPU进入Linux内核来执行由系统调用号a标识的核函数。


关于syscall中参数b,c,d的作用

其实就是系统调用的参数,例如,当你调用 read() 系统调用时,你会提供一个文件描述符(文件的唯一标识)、一个存放数据的缓冲区地址和一个表示要读取的字节数。这些信息就是通过 b、c 和 d 这三个参数传递给 syscall 函数的。


fork()

函数描述
fork()函数是Linux系统中的一个系统调用,它用于创建一个新的进程。新的进程被称为子进程,而调用fork()函数的进程被称为父进程。子进程是父进程的一个副本,它从父进程继承了大部分的环境,例如文件描述符、环境变量和程序计数器等

每个用户在同一时间只能有数量有限的进程。用户资源限制可在/etc/security/limits.conf设置。

用户可运行:

ulimit -a

来查看各种资源限制值。

函数原型

#include <unistd.h>
pid_t fork(void);

各参数说明
fork()函数没有参数。

执行过程
当调用fork()函数时,操作系统会创建一个新的进程。新的进程(子进程)几乎是原进程(父进程)的完全复制品,它们的代码、数据和堆栈等都是一样的。

本质上就是复制进程映像

但是,子进程有自己的进程标识符,它的许多值(如某些资源使用量)也被设置为初始值。子进程会继承父进程的用户ID和组ID,继承父进程的文件模式创建屏蔽字,继承父进程的信号处理方式等。

底层实现
fork()函数的具体实现依赖于操作系统,一般来说,它会调用内核中的一些函数(主要为kfork())来分配资源(如内存),复制父进程的状态,并在进程表中创建一个新的条目。

执行示例

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

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        printf("Fork failed!\n");
    } else if (pid == 0) {
        printf("This is the child process, with id %d\n", getpid());
    } else {
        printf("This is the parent process, with id %d\n", getpid());
    }

    return 0;
}

返回值
fork()函数的返回值是一个类型为pid_t的值。如果fork()调用成功,那么在父进程中fork()返回新创建的子进程的进程ID;在子进程中,fork()返回0。如果fork()调用失败,它将在父进程中返回-1。

执行结果
在父进程中,fork()函数返回新创建的子进程的进程ID。在子进程中,fork()函数返回0。如果创建新进程失败,fork()函数将返回-1,并设置errno以指示错误。常见的错误包括EAGAIN(系统限制了可以创建的进程总数)和ENOMEM(没有足够的内存来创建新的进程数据结构)。

根据fork的执行属性,可以发现根据fork的返回值是判断当前进程(是子进程还是父进程)的唯一方法

因此可以得出判断父进程和子进程的程序框架:

int pid = fork();
if (pid) {
	// parent executes this part
} else {
	// child executes this part
}
进程执行顺序

fork()执行结束之后,父进程和子进程与系统中的其他进程竞争CPU的运行时间。

哪一个任务率先完成并没有顺序,取决于它们的调度优先级优先级呈现出动态化

下面举出例子:

/*************************************************************************
	> File Name: fork.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Thu 25 Jan 2024 04:21:54 PM CST
	> Describe:
        Describes the order in which the fork() function generates the 
        parent-child process
 ************************************************************************/

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

int main() {
    int pid = fork();
    if (pid) {
        printf("I'm PARENT %d, my CHILD = %d\n", getpid(), pid);
        //sleep(1);
        printf("PARENT %d EXIT\n", getpid());
    } else {
        printf("I'm CHILD %d ,my PARENT = %d\n", getpid(), getppid());
        //sleep(2);
        printf("child %d exit my parent = %d \n", getpid(), getppid());
    }
    return 0;
}

读者可以通过给上文代码的sleep()取消和添加注释来查看父子进程的执行顺序,有以下几种情况:

  • 全部注释:多次运行结果
    进程运行结果
    第一种情况是先执行父进程,再执行子进程,第二种情况是先执行父进程,父进程结束后,shell进程弹出命令,此时子进程结束,归根结底还是父进程先执行,子进程再执行。值得注意的是,会发现情况中出现子进程的parent = 1的情况,这种情况在后文解释

  • 取消父进程注释,子进程注释保留:多次运行发现只有一种情况
    进程运行结果
    运行时会发现前三行先输出,1秒之后最后一行输出。下面是具体的执行细节:
    首先,父进程调用fork()函数,此时,进程一分为2,由于此时父进程没有退出CPU,因此继续执行父进程,父进程执行sleep函数,调度器认为此时可以将父进程挂起,进而去执行子进程,此时子进程进入CPU,此时输出第二、第三行,子进程运行结束,1秒后父进程返回CPU继续执行输出最后一句话

  • 取消第二行注释,但不取消第一行注释:多次运行后结果如下:
    程序运行结果
    与上面的执行类似,CPU先执行父进程,创建子进程之后,继续运行父进程然后结束,此时切换至shell进程执行,然后再切换至子进程运行输出,等待一秒之后输出最后一行信息。这里也发现子进程的parent = 1的情况,这些情况放到后面来说。

  • 取消所有注释:运行结果和第二种方式一样:
    程序运行结果
    不同的细节在于,这种情况下,先输出前两句,1秒后输出第三句,1秒后输出第四句,读者可以猜想一下具体执行过程。

除了sleep函数可以让进程延迟几秒之外,Linux/Uinx还提供了以下几种函数会影响进程执行顺序:

  • nice(int inc)
  • sched_yield(void)

下面给出详细信息,读者不需要细看,等到深入学习之后可以再细究

nice()

函数描述
nice()函数是用于调整进程运行的优先级。在Linux中,进程运行的优先级分为-20~19等40个级别,其中,数值越小运行优先级越高,数值越大运行优先级越低。函数nice()是将当前进程运行的优先级增加指定值,即用当前进程运行的优先级加上指定值得到新的优先级,然后用新的优先级运行该进程。当计算出来的值小于-20,则进程将以优先级-20运行;当计算出来的值大于19,则进程将以优先级19运行。

函数原型

int nice(int inc);

各参数说明

  • inc:指定优先级增加的值。若增加正值,则表示降低进程运行优先级;若增加负值,则表示升高进程运行优先级。但只有具有超级用户权限的用户才可以以负数作为函数的参数,否则该函数将返回错误。

执行过程
nice()函数将当前进程运行的优先级增加指定值,即用当前进程运行的优先级加上指定值得到新的优先级,然后用新的优先级运行该进程。

底层实现
关于nice()函数的底层实现,它是通过修改进程的优先级来实现的。在Linux中,进程的优先级是由一个介于-20到19的整数来表示的,这个整数越小,进程的优先级就越高。nice()函数就是通过增加这个整数(即降低优先级)或减少这个整数(即提高优先级)来改变进程的优先级。

执行示例

#include <stdio.h> /* printf */
#include <stdlib.h> /* atoi, system, exit */
#include <errno.h> /* errno */
#include <string.h> /* strerror */
#include <unistd.h> /* nice */

int main (int argc, char *argv [])
{
    int adjustment = 0;
    int ret;
    if (argc > 1) {
        adjustment = atoi(argv[1]);
    }
    ret = nice(adjustment);
    printf("nice(%d):%d\n", adjustment, ret);
    if (-1 == ret) {
        if (errno == EACCES) {
            printf("Cannot set priority:%s.\n", strerror(errno));
            exit(-1);
        }
    }
    system("nice");
    exit(0);
}

返回值
若操作成功,函数将返回调整后的进程运行的优先级;若操作失败,函数将返回-1。注意:当函数返回-1时,不一定就是函数操作失败。因为若函数成功调整进程运行优先级后的优先级为-1,函数也返回-1,所以在判断函数是否操作失败时,除了判断函数返回的值是否为-1外,还需要查看errno的值是否为相关错误码。

执行结果
执行结果取决于nice()函数是否成功调整了进程的优先级。如果成功,那么进程的优先级将被调整,如果失败,那么进程的优先级将保持不变。如果调用nice()函数的用户没有超级用户权限,但是试图提高进程的优先级(即inc参数为负数),那么nice()函数将返回错误,并且errno将被设置为EACCES

sched_yield

函数描述
sched_yield()函数的作用是让出处理器,调用时会导致当前线程放弃CPU,进程管理系统会把该线程放到其对应优先级的CPU静态进程队列的尾端,然后一个新的线程会占用CPU。

函数原型

#include <sched.h>
int sched_yield(void);

此函数没有参数。

执行过程
sched_yield()函数可以使另一个级别等于或高于当前线程的线程先运行。如果没有符合条件的线程,那么这个函数将会立刻返回然后继续执行当前线程的程序。

底层实现
在Linux 2.6以前的版本中,sched_yield()所造成的影响非常小,如果存在另一个可以运行的进程,内核就切换到该进程,把进行调用的进程放在可运行进程列表的结尾处。短期内内核会对该进程进行重新调度。这样的话可能出现“乒乓球”现象,也就是两个程序来回运行,直到他们都运行结束。2.6版本中做了一些改变:如果进程是RR,把它放到可运行进程结尾,返回。否则,把它从可运行进程列表移除,放到到期进程列表,这样在其他可运行进程时间片用完之前不会再运行该进程。从可执行进程列表中找到另一个要执行的进程。

执行示例

#include <stdio.h>
#include <sched.h>

int main() {
    int ret = sched_yield();
    if (ret == -1) {
        printf("调用sched_yield失败!\n");
    }
    return 0;
}

返回值
在成功完成之后返回零,否则返回-1。

执行结果
执行结果取决于sched_yield()函数是否成功让出了CPU。如果成功,那么当前线程的CPU占有权将被让出,然后把线程放到静态优先队列的尾端,然后一个新的线程会占用CPU。如果失败,那么当前线程将继续执行。

进程终止

执行程序映像的进程可以有两种方式终止:

  • 正常终止
  • 异常终止

正常终止

每个C程序的main()函数,都是由C启动代码crt0.o调用的。如果main函数执行成功,那么函数将会返回到crt0.o,然后调用库函数的exit(0)来终止进程

exit()函数的执行过程具体如下:

首先,执行清理工作,刷新stdout(此时其中没有被输出的信息将会被全部输出,具体内容请看你真的理解printf函数吗)、关闭I/O流等。然后发出一个系统调用_exit(value),使进入内核态的进程终止。退出值0通常表示正常终止。

如果需要,你可以在程序的任何位置调用exit(),不必返回到crt0.o,再直接一点的话,进程可能会发出_exit(value)系统调用立即执行终止,不必先进行清理工作。当内核中的某个进程终止时,它会将_exit(value)系统调用中的值记录到进程PROC结构体中的状态字(status = EXIT),并通知它的父进程并使该进程成为僵尸进程。父进程可通过系统调用wait()找到僵尸进程,获得其PID和退出状态。

pid = wait(int *status);

它还会清空僵尸子进程PROC结构体,使得该结构可被另一个进程重用。

wait() 等待子进程终止

在任何时候,一个进程都可以调用

int pid = wait(int *status);

函数描述
wait()函数是用于让父进程等待子进程结束。当子进程结束后,wait()函数会收集子进程的信息(exitCode),并将其彻底销毁。如果没有找到已经结束的子进程,wait()函数会一直阻塞,直到有一个子进程结束。

函数原型

#include <sys/types.h> /* 提供类型pid_t的定义 */
#include <sys/wait.h>
pid_t wait(int *status);

各参数说明

  • status:指向int类型的指针,用于保存被收集进程退出时的状态。如果我们对子进程是如何结束的不在乎,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为NULL。

执行过程
进程一旦调用了wait(),就立即阻塞自己,由wait()自动分析是否当前进程的某个子进程已经退出。如果让它找到了这样一个已经变成僵尸的子进程,wait()就会收集这个子进程的信息,并把它彻底销毁后返回。如果没有找到这样一个子进程,wait()就会一直阻塞在这里,直到有一个出现为止。

底层实现
wait()函数的底层实现主要是通过阻塞父进程,等待子进程的结束。具体来说调用了内核中的kwait()函数,当子进程结束后,wait()函数会收集子进程的信息,并将其彻底销毁。这样可以防止子进程变成僵尸进程。

执行示例

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

int main() {
    pid_t pc, pr;
    int status;

    pc = fork();
    if (pc < 0) {
        printf("fork error!\n");
    } else if (pc == 0) {
        printf("This is child process with pid of %d\n", getpid());
        sleep(10);
    } else {
        pr = wait(NULL);
        printf("I catched a child process with pid of %d\n", pr);
    }

    return 0;
}

返回值
如果成功,wait()会返回被收集的子进程的进程ID。如果调用进程没有子进程,调用就会失败,此时wait()返回-1,同时errno被置为ECHILD

执行结果
执行结果取决于wait()函数是否成功收集了子进程的信息并将其销毁。如果成功,那么子进程的信息将被收集并销毁,父进程将继续执行。如果失败,那么父进程将继续阻塞,直到有一个子进程结束。

异常终止

当进程遇到错误(如非法指令、越权、除零等)时,这些错误被CPU标记为异常

当某个进程遇到异常时,会陷入操作系统内核。

内核的异常处理程序将陷阱错误类型转换为一个幻数,称为信号,将信号传递给进程,使得进程终止。在这种情况下,僵尸进程的退出状态是信号编号

除了陷阱错误,信号也可能来自硬件或其他进程。例如:按下"Crtl + C"组合键会产生一个硬件中断信号,它会向该终端上的所有进程发送这个信号,使进程终止。

除此之外,也可以使用命令:

kill -s singnl_number pid

向通过pid识别的目标进程发送信号。

进程终止时,最终都会在操作系统内核中调用kexit()函数,这里不再细讲,以后我会出详细文章再做介绍。

每个PROC都有一个2字节的退出代码(exitCode)字段,用于记录进程退出状态。如果进程正常终止,exitCode的高位字节是_exit(exitValue)系统调用中的exitValue,低位字节是导致异常终止的的信号数值。因为一个进程只能死亡一次,所以只有一个字节有意义。

等待子进程终止

接下来根据一个示例程序分析理解wait()函数的作用:

/*************************************************************************
	> File Name: wait.c
	> Author:Royi 
	> Mail:royi990001@gmail.com 
	> Created Time: Thu 25 Jan 2024 07:09:56 PM CST
	> Describe: 
 ************************************************************************/

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


void main() {
    pid_t pid;
    int status;
    pid = fork();
    if (pid) {
        printf("I'm Parent %d, waitting for child %d to DIE\n", getpid(), pid);
        pid = wait(&status);
        printf("DEAD child = %d, status = 0x%04x\n", pid, status);
    } else {
        printf("child %d dies by exit(VALUE)\n", getpid());
        exit(100);
    }
    return ;
}

运行结果如下:

wait函数程序示例运行结果
由于有wait()函数的存在,程序不会出现因为进程优先级的动态顺序而出现不同进程先运行的情况。

可以看出status = 0x6400,上文提到每个PROC都有两个字节的退出代码字段,在程序中,我们调用的exit(100)中的100就是这个退出字段,表现在status中就是其高两位(0x64 = 100)

除此之外,某个进程还可以使用系统调用:

int pid = waitpid(int pid, int *status, int options);

等待由pid指定的具有多个选项的特定僵尸子进程。

wait(&status) 等于waitpid(-i, &status, 0),详细内容读者可以参考Linux手册页。

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

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

相关文章

HCIP 三层交换

拓扑图&IP划分如下&#xff1a; 第一步&#xff0c;配置IP 以R1为例&#xff0c;R2同理 LSW1&#xff0c;LSW2同理 第二步&#xff0c;聚合链路 LSW1&#xff0c;LSW2同理 修改串口 LSW1&#xff0c;LSW2同理 第三步&#xff0c;配置生成树 LSW1&#xff0c;LSW2同理 …

听力下降为什么会影响到言语感知?

一、听力障碍对阈值、听觉频率范围和分辨能力的影响 听力障碍使得听障者的听敏度降低&#xff0c;提高了阈值&#xff0c;不利于言语信号的接收。听障者听力阈值的变化在不同频率并不相同&#xff0c;一般而言&#xff0c;高频部分的听力损失往往大于低频部分&#xff0c;而言…

代码随想录刷题笔记-Day11

1. 逆波兰表达式求值 150. 逆波兰表达式求值https://leetcode.cn/problems/evaluate-reverse-polish-notation/description/ 给你一个字符串数组 tokens &#xff0c;表示一个根据 逆波兰表示法 表示的算术表达式。 请你计算该表达式。返回一个表示表达式值的整数。 注意&a…

区块链技术在教育领域的应用:Web3教育变革

随着Web3时代的来临&#xff0c;区块链技术在各个领域都展现出了巨大的潜力&#xff0c;而在教育领域&#xff0c;区块链的应用正引领着一场教育变革。本文将深入探讨区块链技术在教育领域的创新应用&#xff0c;以及这一应用如何推动Web3时代的教育变革。 1. 学历和成绩的去中…

Shell脚本⑤函数与数组

一.函数 封装的可重复利用的具有特定功能的代码 格式&#xff1a; 方法一&#xff1a; [function] 函数名 (){ 命令序列 [return x] #使用return或者exit可以显式的结束函数 } 方法二&#xff1a; 函数名(){ 命令序列 } 1.函数的调用方法 &#xff08;1&…

【K8S 云原生】K8S的安全机制

目录 一、K8S安全机制概述 1、概念 2、请求apiserver资源的三个步骤&#xff1a; 一、认证&#xff1a;Anthentcation 1、认证的方式&#xff1a; 1、HTTP TOKEN&#xff1a; 2、http base&#xff1a; 3、http证书&#xff1a; 2、认证的访问类型&#xff1a; 3、签发…

【Power Platform】实现让审批人可以修改其他人提交的表单中的部分字段

之前我们分享的案例里&#xff0c;我提了一嘴我们的客户有一个需求&#xff0c;就是审批人要有能力修改其他人表单中的部分字段。 今天我们就来分享一下如何实现这个功能。 要实现这个效果&#xff0c;我们需要判断三个值——当前审批人是不是当前登录人、当前审批节点可以修…

基于python京东商品数据采集与可视化分析大屏设计与实现

随着电子商务行业的快速发展&#xff0c;京东作为中国最大的综合性电商平台之一&#xff0c;拥有海量的商品数据。对这些数据进行采集与分析&#xff0c;能够帮助企业了解市场趋势、消费者需求以及产品销售情况&#xff0c;为决策提供科学依据。 本文旨在基于京东商品数据的采…

Linux设备树中的 gpio 信息

一. 简介 前面几篇文章讲解了 pinctrl 子系统&#xff0c; pinctrl 子系统重点是设置 PIN( 有的 SOC 叫做 PAD) 的复用 和电气属性。 注意&#xff1a;如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话&#xff0c;那么接下来就要用到 gpio 子系统了。如果 PIN用作其他…

若依微服务框架 上传文件(文件表)

若依微服务得上传文件只有在头像那里才有&#xff0c;而且存储得是地址。 如果想要进行文件表存储&#xff0c;只能自己进行封装。 若依微服务框架 上传文件&#xff08;文件表&#xff09; 一、问题二、代码1.组件代码2、调用 一、问题 若依在上传文件这里使用了watch监听&a…

Webpack5 基本使用 - 1

Webpack 是什么 webpack 的核心目的是打包&#xff0c;即把源代码一个一个的 js 文件&#xff0c;打包汇总为一个总文件 bundle.js。 基本配置包括mode指定打包模式&#xff0c;entry指定打包入口&#xff0c;output指定打包输出目录。 另外&#xff0c;由于 webpack默认只能打…

R语言批量把数值变量和因子变量的互转

#我们以rms包的lung数据集为例 library(rms) data<-lung #这里有两种方法&#xff0c; #第1是知道需要转化的变量在哪几列&#xff1b; #第2知道需要转化的变量名 str(data) #假设我们想转化inst/status/sex/三个变量的类型 #图1先看看变量类型和处于第几列 str(dat…

【C++11并发】mutex 笔记

简介 在多线程中往往需要访问临界资源&#xff0c;C11为我们提供了mutex等相关类来保护临界资源&#xff0c;保证某一时刻只有一个线程可以访问临界资源。主要包括各种mutex&#xff0c;他们的命名大都是xx_mutex。以及RAII风格的wrapper类&#xff0c;RAII就是一般在构造的时…

VRRP6协议--负载均衡配置

VRRP6负载均衡 VRRP6负载均衡指的是创建多个备份组,多个备份组同时承担数据转发的任务,对于每一个备份组,都有自己的Master和若干Backup设备。 VRRP6负载分担与VRRP6主备备份的基本原理和报文协商过程都是相同的。同样对于每一个VRRP6备份组,都包含一个Master设备和若干Ba…

蓝桥杯备战——7.DS18B20温度传感器

1.分析原理图 通过上图我们可以看到DS18B20通过单总线接到了单片机的P14上。 2.查阅DS18B20使用手册 比赛的时候是会提供DS18B20单总线通讯协议的代码&#xff0c;但是没有提供读取温度数据的代码&#xff0c;所以还是需要我们去查看手册&#xff0c;我只把重要部分截下来了 …

幻兽帕鲁搭建私服,一键更新方法

看着帕鲁这么火&#xff0c;估计更新会变为常态了&#xff0c;如果有自己搭建私服的话&#xff0c;跟着我下面的方法去进行更新吧&#xff01; 如果你还没有自己的私服&#xff0c;快去三五十搞一个吧&#xff0c;只需三五分钟&#xff0c;叫上你的小伙伴一起去搞起来吧 只需3分…

计算机网络体系架构认知--网络协议栈

文章目录 一.计算机网络分层架构各协议层和计算机系统的联系从整体上理解计算机网络通信计算机网络通信的本质 二.Mac地址,IP地址和进程端口号三.局域网通信与跨局域网通信局域网通信跨局域网通信全球互联的通信脉络 四.网络编程概述 一.计算机网络分层架构 实现计算机长距离网…

25考研每日的时间安排

今天要给大家分享一下25考研每日的时间安排。 没有完美的计划&#xff0c;只有合适的计划。 仅供参考 很多人说复习不要只看时长而是要看效率&#xff0c;所以学多长时间不重要&#xff0c;重要的高效率完成任务。 完美的计划 这个计划看起来很完美&#xff0c;从早到晚有学习…

【产品笔记】ESP32及其物联网硬件设备——ESP32智能网关

ESP32是一款适用于许多物联网应用的强大芯片。本文作为学习笔记&#xff0c;记录ESP32及其衍生产品在物联网中的特点&#xff0c;希望对您选择基于ESP32的物联网网关也能有帮助。 什么是ESP32&#xff1f; 在嵌入式系统和物联网应用领域&#xff0c;ESP32是一款广受欢迎的微控…

【JavaMail】Java中发送邮件

文章目录 一、概念二、Java中发送邮件1.导入2.连接SMTP服务器3.创建Session会话4.发送纯文本邮件5.发送带附件邮件 三、封装工具类 一、概念 首先需要明白以下概念&#xff1a; 不需要深入了解他们是怎么工作的&#xff0c;记住关键字即可&#xff1a; SMTP协议&#xff1a;邮…
最新文章