BIT.4 Linux进程控制

news/2024/4/29 20:31:30/文章来源:https://blog.csdn.net/weixin_54183294/article/details/127106413

目录

  • 进程创建
    • fork函数初识
    • 写实拷贝
    • fork常规用法
    • fork调用失败的原因
    • 补充知识
  • 进程终止
    • 进程退出场景
    • 进程常见退出方法
    • exit函数与_exit函数
    • return 退出
    • 补充知识
  • 进程等待
    • 进程等待必要性
    • 进程等待的方法
      • wait方法
      • waitpid方法
      • wait / waitpid 阻塞代码
      • WIFEXITED
      • wait / waitpid 非阻塞代码
    • 获取子进程status
  • 进程程序替换
    • 替换原理
    • 替换函数
    • 函数解释
    • 命名理解
    • makefile 生成多个文件
    • 简易的shell
  • 函数和进程之间的相似性

进程创建

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    在这里插入图片描述
    当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以
    开始它们自己的旅程,看如下程序。
  1 #include<stdio.h>2 #include<unistd.h>3 4 int main()5 {6   printf("我是父进程\n");7   8   pid_t id = fork(); // 调用fork函数创建子进程                                9   if(id < 0)10   {11     printf("创建子进程失败\n");12     return 1;13   }14   else if(id == 0)15   {16     while(1)17     {18       printf("我是子进程: pid: %d, ppid: %d\n", getpid(), getppid());19       sleep(1);20     }21   }                 22   else                 23   {                                     24     while(1)           25     {26       printf("我是父进程: pid: %d, ppid: %d\n", getpid(), getppid());27       sleep(1);                                                  28     }                                                     29   }                                   30   return 0;                                           31 }     // 运行结果我是父进程
我是父进程: pid: 26264, ppid: 22610
我是子进程: pid: 26265, ppid: 26264
我是父进程: pid: 26264, ppid: 22610
我是子进程: pid: 26265, ppid: 26264
我是父进程: pid: 26264, ppid: 22610
.........
.........  

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

写实拷贝

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

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

补充知识

在这里插入图片描述

进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):(注意echo $?是获取最近一个程序执行完毕的退出码)

  1. 从main返回
  2. 调用exit
  3. _exit
    异常退出:
  • ctrl + c,信号终止

exit函数与_exit函数

  1 #include<stdio.h>2 #include<string.h>3 #include<unistd.h>4 #include<stdlib.h>5 int main()6 {7   printf("你可以看到这里的打印信息码?"); // 注意这里没有换行8   sleep(3);9   exit(13);                                                                  10 }
  • 上述exit调用后的现象
    在这里插入图片描述
  1 #include<stdio.h>2 #include<string.h>3 #include<unistd.h>4 #include<stdlib.h>5 int main()6 {7   printf("你可以看到这里的打印信息码?");8   sleep(3);9   _exit(13);                                                                  10 }
  • 上述_exit调用后的现象
    在这里插入图片描述

在这里插入图片描述
exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

return 退出

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返
回值当做 exit的参数。

补充知识

查看退出码对应的错误信息

 12 int main()                                                                    13 {14   int i = 0;15   for(i = 0; i < 100; i++) // i < 100 是打印出100条,这里的数值可以增加16   {17     printf("%d: %s\n",i, strerror(i));18   }19   return 0;20 }// 结果
0: Success
1: Operation not permitted
2: No such file or directory
3: No such process
4: Interrupted system call
5: Input/output error
6: No such device or address
7: Argument list too long
8: Exec format error
9: Bad file descriptor
10: No child processes
11: Resource temporarily unavailable
12: Cannot allocate memory
13: Permission denied
14: Bad address
15: Block device required
16: Device or resource busy
.....
.....

在这里插入图片描述

进程等待

进程等待必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

wait方法

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:成功返回被等待进程pid,失败返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid方法

pid_ t waitpid(pid_t pid, int *status, int options);
返回值:当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数: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,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

wait / waitpid 阻塞代码

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int code = 0;int main()
{pid_t id = fork();if(id < 0){perror("fork");exit(1); //标识进程运行完毕,结果不正确}else if(id == 0){//子进程int cnt = 5;while(cnt){printf("cnt: %d, 我是子进程, pid: %d, ppid : %d\n", cnt, getpid(), getppid());sleep(1);cnt--;}exit(15);}else{//父进程printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());// pid_t ret = wait(NULL); //阻塞式的等待!// pid_t ret = waitpid(id, NULL, 0); //阻塞式的等待! ==  wait(NULL)  option默认为0, 表示阻塞等待int status = 0;pid_t ret = waitpid(id, &status, 0); //阻塞式的等待!if(ret > 0){// 0x7F -> 0000.000 111 1111printf("等待子进程成功, ret: %d, 子进程收到的信号编号: %d,子进程退出码: %d\n",\ret, status & 0x7F ,(status >> 8)&0xFF); //0xff --> 0000...000 1111 1111printf("code: %d\n", code);}// 阻塞式的等待,父进程不会执行下面代码,会阻塞等待子进程,当子进程结束,获取wait返回的结果// 再执行下面的代码while(1){printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}}
}

WIFEXITED

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{pid_t id = fork();if (id < 0){perror("fork()");exit(1);}else if (id == 0){// 子进程printf("我是子进程: PID %d, PPID %d\n", getpid(), getppid());}else{// 父进程int status = 0;// 只有子进程退出的时候,父进程才会waitpid函数,进行返回!![父进程依旧还活着呢!!]// waitpid/wait 可以在目前的情况下,让进程退出具有一定的顺序性!// 将来可以让父进程进行更多的收尾工作.// id > 0 : 等待指定进程// id == -1: 等待任意一个子进程退出, 等价于wait();// options: 默认为0,代表阻塞等待, WNOHANG: 叫做非阻塞等待pid_t result = waitpid(id, &status, 0); // 默认是在阻塞状态去等待子进程状态变化(就是退出!)if (result > 0){// 可以不这么检测//printf("父进程等待成功, 退出码: %d, 退出信号: %d\n", (status>>8)&0xFF, status & 0x7F);if (WIFEXITED(status)) // WIFEXITED(status): 若为正常终止子进程返回的状态,则为真{//子进程是正常退出的// 若WIFEXITED非零,提取子进程退出码printf("子进程执行完毕,子进程的退出码: %d\n", WEXITSTATUS(status));}else{// 子进程是不是正常退出的printf("子进程异常退出: %d\n", WIFEXITED(status));}}}
}

wait / waitpid 非阻塞代码


#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <unistd.h>typedef void (*handler_t)(); //函数指针类型std::vector<handler_t> handlers; //函数指针数组void fun_one()
{printf("这是一个临时任务1\n");
}
void fun_two()
{printf("这是一个临时任务2\n");
}// 设置对应的方法回调
// 以后想让父进程闲了执行任何方法的时候,只要向Load里面注册,就可以让父进程执行对应的方法喽!
void Load()
{handlers.push_back(fun_one);handlers.push_back(fun_two);
}int main()
{pid_t id = fork();if (id == 0){// 子进程int cnt = 5;while (cnt){printf("我是子进程: %d\n", cnt--);sleep(1);}exit(11); // 11 仅仅用来测试}else{int quit = 0;while (!quit){int status = 0;pid_t res = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待if (res > 0){//等待成功 && 子进程退出printf("等待子进程退出成功, 退出码: %d\n", WEXITSTATUS(status));quit = 1;}else if (res == 0){//等待成功 && 但子进程并未退出printf("子进程还在运行中,暂时还没有退出,父进程可以在等一等, 处理一下其他事情??\n");if (handlers.empty()) Load();for (auto iter : handlers){//执行处理其他任务iter();}}else{//等待失败printf("wait失败!\n");quit = 1;}sleep(1);}}
}

在这里插入图片描述

获取子进程status

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

在这里插入图片描述

在这里插入图片描述

进程程序替换

替换原理

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

(exce函数是程序替换,调用该程序成功之后,回将当前程序的所有代码和数据进行替换,包括执行的和没有执行的,所以一旦调用成功,后续所有代码去不不会执行!)
(程序替换是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间)
(进程替换,没有创建新的子进程,就是将磁盘的程序加载到内存,并和当前进程的页表重新建立映射)
在这里插入图片描述

替换函数

其实有六种以exec开头的函数,统称exec函数:

#include <unistd.h>`
// const char *path是为了找到程序! path: 路径 + 目标文件名
//  【 const char *arg, ... 】  可变参数列表, 可以传入多个不定个数的参数
// 注意最后一个参数, 必须为  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 execve(const char *path, char *const argv[], char *const envp[]);

函数解释

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

命名理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

在这里插入图片描述

int main()
{printf("当前进程的开始代码!\n");//execl("/usr/bin/ls", "ls", NULL);execl("/usr/bin/ls", "ls", "--color=auto","-l", NULL);// exce函数失败直接退出exit(1); //execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);//execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);//execl("/usr/bin/top", "top", NULL);printf("当前进程的结束代码!\n");return 0;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// ./mycmd -a/-b/-c...
int main(int argc, char *argv[])
{if(argc != 2){printf("can not execute!\n");exit(1);}//MY_105_VAL: 目前并不存在!printf("获取环境变量: MY_105_VAL: %s\n", getenv("MY_105_VAL"));if(strcmp(argv[1], "-a") == 0){printf("hello a!\n");}else if(strcmp(argv[1], "-b") == 0){printf("hello b!\n");}else{printf("default!\n");}return 0;
}
/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>#define NUM 16//const char *myfile = "/home/whb/105/online-code---phase-105/lesson17/mycmd"; // mycmd是上面代码生成的二进制文件
const char *myfile = "./mycmd"; // 相对路径int main(int argc, char*argv[], char *env[])
{// 为什么我要创建子进程?// 为了不影响父进程// 我们想让父进程聚焦在读取数据,解析数据,指派进程执行代码的功能!// 如果不创建,那么我们替换的进程只能是父进程,如果创建了,替换的进程就是子进程,而不影响父进程(为什么?)//while(1)//{//1. 显示一个提示行: root@localhost# //2. 获取用户输入的字符串,fgets,scanf。 -> ls -a -l//3. 对字符串进行解析//4. 下面的代码char *const _env[NUM] = {(char *)"MY_105_VAL=888777666555",NULL};pid_t id = fork();if(id == 0){//子进程//ls -a -lprintf("子进程开始运行, pid: %d\n", getpid());sleep(3);char *const _argv[NUM] = {(char*)"ls",(char*)"-a",(char*)"-l",(char*)"-i",NULL};// 自己写的环境变量_env, 会将它传给 mycmd, 里面可以获取到,从这里我们可以想到,// 环境变量具有全局属性的本质是就是通过execle, 将环境变量传给子进程execle(myfile, "mycmd", "-a", NULL, _env);//execle(myfile, "mycmd", "-a", NULL, env);//execle(myfile, "mycmd", "-a", NULL); // 也可以运行//execlp("./test.py", "test.py", NULL);//execlp("bash", "bash", "test.sh", NULL);//execlp("python", "python", "test.py", NULL);//execl(myfile, "mycmd", "-b", NULL);//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//execv("/usr/bin/ls", _argv); //和上面的execl只有传参方式的区别//execlp("ls", "ls", "-a", "-l", NULL);//execvp("ls", _argv);exit(1);}else {//父进程printf("父进程开始运行, pid: %d\n", getpid());int status = 0;pid_t id = waitpid(-1, &status, 0); //阻塞等待, 一定是子进程先运行完毕,然后父进程获取之后,才退出!if(id > 0){printf("wait success, exit code: %d\n", WEXITSTATUS(status));}}//} //end while(1)return 0;
}

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在
man手册第3节。这些函数之间的关系如下图所示。

下图exec函数族 一个完整的例子:
在这里插入图片描述

makefile 生成多个文件

在这里插入图片描述

简易的shell

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>#define NUM 1024
#define SIZE 32
#define SEP " "//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串
char *g_argv[SIZE];// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{//0. 命令行解释器,一定是一个常驻内存的进程,不退出while(1){//1. 打印出提示信息 [whb@localhost myshell]# printf("[root@localhost myshell]# ");fflush(stdout);memset(cmd_line, '\0', sizeof cmd_line);//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL){continue;}cmd_line[strlen(cmd_line)-1] = '\0';//"ls -a -l -i\n\0"//printf("echo: %s\n", cmd_line);//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串int index = 1;if(strcmp(g_argv[0], "ls") == 0){g_argv[index++] = "--color=auto";}if(strcmp(g_argv[0], "ll") == 0){g_argv[0] = "ls";g_argv[index++] = "-l";g_argv[index++] = "--color=auto";}//?while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL//for debug//for(index = 0; g_argv[index]; index++)//    printf("g_argv[%d]: %s\n", index, g_argv[index]);//4. TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令//内建命令本质其实就是shell中的一个函数调用if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute{if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..continue;}//5. fork()pid_t id = fork();if(id == 0) //child{printf("下面功能让子进程进行的\n");//cd cmd , current child pathexecvp(g_argv[0], g_argv); // ls -a -l -iexit(1);}//fatherint status = 0;pid_t ret = waitpid(id, &status, 0);if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));}
}

函数和进程之间的相似性

exec/exit就像call/return
一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的
操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。
这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux鼓励将这种应用于程
序之内的模式扩展到程序之间。如下图
在这里插入图片描述
一个C程序可以fork/exec另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过exit(n)来
返回值。调用它的进程可以通过wait(&ret)来获取exit的返回值。

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

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

相关文章

客快物流大数据项目(九十八):ClickHouse的SQL函数

文章目录 ClickHouse的SQL函数 一、​​​​​​​​​​​​​​类型检测函数

Redis集群之AKF架构原理

当我们搭建集群之前&#xff0c;先要想明白需要解决哪些问题&#xff0c;搞清楚这个之前先回想一下单节点、单实例、单机有哪些问题&#xff1f; 单点故障&#xff1a;只有一台Redis的话&#xff0c;如果出现故障&#xff0c;那么整个服务都不可用缓存容量&#xff1a;单台Red…

【Django】第一课 基于Django超市订单管理系统开发

概念 django服务器开发框架是一款基于Python编程语言用于web服务器开发的框架&#xff0c;采用的是MTV架构模式进行分层架构。 项目搭建 打开pycharm开发软件&#xff0c;打开开发软件的内置dos窗口操作命令行 在这里指定项目存放的磁盘路径&#xff0c;并使用创建django项…

54三数之和55 56有无重复元素的全排列

54 三数之和 首先想到的就是之前的两数之和&#xff0c;只要在外层遍历一遍&#xff0c;对每个元素用之前的两数之和的哈希做法&#xff0c;就刚好是O(n^2) 但是有坑的地方在于需要去重&#xff0c;并且输出的三元组也是需要顺序的&#xff01;&#xff01;然后我用set去重和重…

史上最强,这份在各大平台获百万推荐的Java核心手册实至名归

又逢“金九银十”&#xff0c;年轻的毕业生们满怀希望与忐忑&#xff0c;去寻找、竞争一个工作机会。已经在职的开发同学&#xff0c;也想通过社会招聘或者内推的时机争取到更好的待遇、更大的平台。 然而&#xff0c;面试人群众多&#xff0c;技术市场却相对冷淡&#xff0c;…

flutter 环境搭建

一、简介 Flutter 是谷歌开发的一款开源、免费的&#xff0c;基于 Dart 语言的U1框架,可以快速在i0S和Android上构建高质量的原生应用。 它最大的特点就是跨平台和高性能。Dart是由谷歌&#xff0c;在2011 年开发的计算机编程语言&#xff0c;它可以被用于Web、服务器、移动应…

服务注册配置中心Nacos

文章目录一. 前言二. 下载安装1. 下载安装包2. Windows环境安装3. Linux环境安装1. 单击模式启动2. 集群模式启动3. 远程web控制4. 注册为系统服务三. 基本使用1. 添加依赖2. 服务注册3. 配置实例集群属性4. 实例权重负载均衡5. 环境隔离6. 临时实例与非临时实例四. Nacos配置管…

python常用模块

time模块 常用操作 1.直接获取时间 time.time() #获取结果是秒数&#xff0c;即从1970年1月1日8:00起计#1671856010.9592516 2.获取结构化时间 time.localtime() #获取本地时间&#xff0c;中国为东八区&#xff0c;为上海时间 time.gmtime() …

3.2 Static Terrestrial Laser Scanners 静态地基激光扫描仪

本章节介绍的静态地基激光扫描系统指的是那些在一个固定位置的位置上对周边场景地物特征进行扫描的设备。该类型设备的扫描测量机制是&#xff0c;通过激光测距仪进行斜距测量&#xff0c;与此同时通过水平和竖直两个方向上同步运动的角度编码器来记录角度变化值&#xff08;如…

C#大型医院HIS系统源码 医院信息管理系统源码 C/S架构 VS2013+sql2012

了解更多源码内容&#xff0c;可私信我。 开发环境&#xff1a;VS2013sql2012 C/S架构 一、门诊系统&#xff1a; 1、挂号与预约系统:实现了医院门诊部挂号处所需的各种功能&#xff0c;包括门诊安排的管理&#xff0c;号表的生成及维护&#xff0c;门诊预约管理和挂号处理&…

一文带你深入理解【Java基础】· 网络编程(下)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

AQS学习

1.1 AQS 简单介绍 AQS 的全称为&#xff08;AbstractQueuedSynchronizer&#xff09;&#xff0c;这个类在 java.util.concurrent.locks 包下面。 AQS 是一个用来构建锁和同步器的框架&#xff0c;使用 AQS 能简单且高效地构造出应用广泛的大量的同步器&#xff0c; 比如我们提…

五、Arduino IDE开发esp8266环境搭建

1、安装驱动程序 (1)安装USB转串口驱动程序。 (2)根据板载的USB转串口驱动芯片选择合适驱动安装。USB转串口芯片负责和电脑之间进行数据通信。 (3)常见USB转串口驱动 CP210x驱动:CP210x USB 至 UART 桥 VCP 驱动器 - 芯科科技 CH340驱动 2、Arduino IDE环境搭建 要想使用Ar…

K8S-存储-Volume

问题 容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出现一些问题。首先&#xff0c;当容器崩溃 时&#xff0c;kubelet 会重启它&#xff0c;但是容器中的文件将丢失——容器以干净的状态&#xff08;镜像最初的状态&#xff09;重新启动。…

【kafka】学习笔记(三)

学习笔记七、Kafka-Eagle 监控7.1 环境准备7.2 Eagle 安装7.3、修改配置文件7.4、添加环境变量7.5、启动Eagle八、Kafka-Kraft 模式8.1、Kafka-Kraft 集群部署8.2、初始化集群数据目录8.3、启动 kafka 集群8.4、测试8.5、集群启动脚本九、SpringBoot集成Kafka七、Kafka-Eagle 监…

支持设备的待机唤醒功能

系统待机唤醒功能 1 说明背景 1.1 需求 支持 GPU 进入低功耗模式&#xff0c;让用户选择降低设备的功耗 1.2 概念 上位词&#xff1a;APM, ACPI 同类词&#xff1a;睡眠模式, S0~S5 下位词&#xff1a;系统挂起, 系统唤醒, 运行时设备电源管理 1&#xff09;ACPI 在计算机…

第10章_索引优化与查询优化

第10章_索引优化与查询优化 都有哪些维度可以进行数据库调优?简言之: 索引失效、没有充分利用到索引——索引建立关联查询太多JOIN (设计缺陷或不得已的需求)——SQL优化服务器调优及各个参数设置(缓冲、线程数等)———调整my.cnf。数据过多――分库分表 关于数据库调优的…

net/http 库的客户端实现(下)

前言 上一篇文章我们讲了 net/http 库客户端 request 的构建&#xff0c;接下来继续讲构建HTTP请求之后的处理操作 net/http 库的客户端实现(上) net/http 库的客户端实现(下) net/http 库的服务端实现 启动事务 构建 HTTP 请求后&#xff0c;接着需要开启HTTP事务进行请…

Python——几个常用的数学函数

1. min()函数&#xff1a;取出给定参数的最小值 说明&#xff1a;获取指定数值或者指定序列中最小值。 print(min(1, 5)) print(min(1, 2, 3, 4, 5, 6)) print(min([2, 3, 4, 5])) 2.max()函数&#xff1a;取出给定参数的最大值 说明&#xff1a;获取指定数值或者指定序列中…

XDocReport使用入门

XDocReport 简介 XDocReport是GitHub上根据麻省理工学院许可证开源的Wrod导出框架。XDocReport可以根据ODT、Doc、Docx文档模板通过模板引擎语法&#xff08;Freemarker、Velocity&#xff09;转换为另外一种格式文档&#xff08;Doc、Docx、XHTML、PDF&#xff09;。 XDocR…