从入门到精通:Linux进程控制

在计算机操作系统中,进程(Process)是一个非常重要的概念。进程控制是操作系统的核心功能之一,对于Linux操作系统尤其如此。本文将详细介绍Linux操作系统中的进程控制,从入门到精通,涵盖进程的创建、终止、等待以及程序替换等内容。

进程创建
fork函数初识

在Linux中,fork函数是创建新进程的最重要函数之一。通过fork函数,一个已存在的进程可以创建一个新进程。新进程称为子进程,而原进程称为父进程。这个过程在操作系统的进程管理中起着至关重要的作用。

当一个进程调用fork函数时,操作系统会进行一系列复杂的操作,包括分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表中,最终返回到用户空间,开始调度器调度。

fork函数的关键在于它在父进程和子进程中分别返回不同的值。在父进程中,fork返回子进程的PID,而在子进程中,fork返回0。如果fork调用失败,则返回-1。这使得父进程和子进程可以根据返回值来执行不同的代码,从而实现并发执行。

#include <unistd.h>
#include <stdio.h>int main(void) {pid_t pid;printf("Before: pid is %d\n", getpid());pid = fork();if (pid == -1) {perror("fork");return 1;}printf("After: pid is %d, fork returned %d\n", getpid(), pid);return 0;
}

在这个示例中,fork函数被调用后,父进程和子进程都会继续执行下面的代码。输出结果显示了fork前后进程的PID变化,这有助于理解fork的工作机制。

fork函数返回值

fork函数中,返回值在父子进程中不同,这一点非常关键。子进程中返回0,而父进程中返回子进程的PID。这使得进程可以根据返回值来区分自己是父进程还是子进程,从而执行不同的逻辑。

子进程返回0,这是因为子进程是由父进程克隆出来的,初始时它的环境与父进程相同,但它从fork调用的返回点开始独立运行。这种机制允许子进程进行独立的操作,而不会影响父进程。

父进程返回子进程的PID,这是为了让父进程可以管理和控制子进程。通过子进程的PID,父进程可以监控子进程的状态、发送信号、等待子进程结束等操作。如果fork调用失败,返回-1,表示无法创建新进程,通常是由于系统资源不足或达到进程数量限制。

写时拷贝

在进程创建过程中,写时拷贝(Copy-On-Write,COW)是一种优化技术。通常,父子进程共享相同的内存页面,直到有一个进程试图写入数据。这时,操作系统才会为该进程分配独立的内存页面,从而避免不必要的内存复制,提高效率。

写时拷贝的实现依赖于内存管理单元(MMU)和页面表。当一个进程试图写入共享页面时,MMU会捕获写操作,并触发页面错误。操作系统内核会处理这个错误,为进程分配新的物理内存,并更新页面表,以确保写操作在独立的内存区域进行。

这种技术在提高系统性能和节省内存资源方面发挥了重要作用。例如,当一个进程创建子进程时,操作系统不需要立即复制整个进程的内存空间,而是通过写时拷贝机制在实际需要时才进行复制。

fork常规用法

在实际应用中,fork函数有两种常见用法。首先,父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程可以作为服务器进程,等待客户端请求,当有请求到达时,通过fork创建子进程来处理该请求,从而实现并发处理。

另一个常见用法是执行不同的程序。例如,父进程调用fork创建子进程后,子进程可以通过exec函数族执行一个全新的程序,而父进程继续执行原来的任务。这种方式常用于shell等命令解释器中,用户输入命令后,shell创建子进程执行该命令,而父进程继续等待下一个命令。

#include <unistd.h>
#include <stdio.h>int main(void) {pid_t pid;if ((pid = fork()) == -1) {perror("fork");return 1;}if (pid == 0) {// 子进程执行的代码printf("This is the child process, pid is %d\n", getpid());} else {// 父进程执行的代码printf("This is the parent process, pid is %d, child pid is %d\n", getpid(), pid);}return 0;
}

在这个示例中,父进程和子进程根据fork的返回值来执行不同的代码段,实现并发执行。

fork调用失败的原因

尽管fork函数非常强大,但在某些情况下可能会调用失败,常见的原因有以下几点:

  1. 系统中有太多的进程:操作系统对同时运行的进程数量有一定的限制。当系统中已经运行了太多的进程时,再次调用fork可能会失败。
  2. 用户进程数量超过限制:操作系统对每个用户可以创建的进程数量也有限制。如果当前用户已经创建了过多的进程,fork调用也可能会失败。

fork调用失败时,函数返回-1,并设置errno变量以指示具体的错误原因。常见的错误包括EAGAIN(系统限制或用户限制导致的资源不足)和ENOMEM(内存不足)。

了解这些原因并采取适当的措施可以避免或减少fork调用失败的情况。例如,通过监控系统资源和进程数量,及时释放不必要的进程,可以提高fork调用的成功率。

进程终止
进程退出场景

进程的生命周期终止有多种原因,通常可以分为正常终止和异常终止两类。正常终止是指进程按照预期完成任务并退出,异常终止则是进程在运行过程中遇到错误或被外部信号强制终止。

正常终止的场景包括:

  1. 代码运行完毕,结果正确:进程按照设计完成了所有任务,正常退出。
  2. 代码运行完毕,结果不正确:进程完成了任务,但结果不符合预期。这种情况下,进程依然正常退出,只是结果不如预期。

异常终止的场景包括:

  1. 代码异常终止:进程在执行过程中遇到不可预知的错误,例如段错误(segmentation fault)或非法指令。
  2. 外部信号终止:进程被用户或其他进程发送的信号(如SIGKILLSIGTERM)强制终止。
进程常见退出方法

进程的退出方法有多种,包括正常终止和异常终止。正常终止是通过程序控制来实现的,而异常终止通常是由于未处理的错误或外部干预。

正常终止

正常终止的几种方式如下:

  1. main函数返回:这是最常见的退出方法,main函数的返回值将作为进程的退出状态。
  2. 调用exit函数:exit函数执行一些清理工作后终止进程。
  3. 调用_exit函数:_exit函数立即终止进程,不进行清理工作。

示例:

#include <stdlib.h>
#include <stdio.h>int main() {printf("Program is exiting normally\n");return 0; // 从main函数返回
}int another_function() {printf("Program is exiting using exit()\n");exit(0); // 调用exit函数
}int another_function_2() {printf("Program is exiting using _exit()\n");_exit(0); // 调用_exit函数
}
异常终止

异常终止的几种情况如下:

  1. 使用信号终止:例如,用户按下Ctrl+C组合键会发送SIGINT信号终止进程。
  2. 程序遇到未处理的错误:例如,访问非法内存地址

会导致段错误,进程被操作系统强制终止。

示例:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>void signal_handler(int signal) {printf("Received signal %d, exiting...\n", signal);exit(1);
}int main() {signal(SIGINT, signal_handler); // 捕获SIGINT信号printf("Running... Press Ctrl+C to terminate\n");while (1) {sleep(1); // 无限循环}return 0;
}
_exit函数

_exit函数是一个系统调用,用于立即终止进程,不进行任何清理工作。与exit函数不同,_exit函数不会调用用户定义的清理函数,也不会刷新标准IO缓冲区。

#include <unistd.h>void _exit(int status);

参数status定义了进程的终止状态,父进程可以通过waitwaitpid获取该值。尽管status是一个整数,但仅有低8位可以被父进程所用。所以,当调用_exit(-1)时,在终端执行$?会发现返回值是255。

示例:

#include <unistd.h>
#include <stdio.h>int main() {printf("This message will not be displayed\n");_exit(0);printf("This message will also not be displayed\n");return 0;
}

在这个示例中,_exit函数立即终止进程,后续的printf语句不会执行。

exit函数

exit函数用于正常终止进程,调用时会进行一些清理工作,如调用通过atexiton_exit注册的清理函数,刷新标准IO缓冲区,并最终调用_exit终止进程。

#include <stdlib.h>void exit(int status);

示例:

#include <stdio.h>
#include <stdlib.h>void cleanup(void) {printf("Cleanup function called\n");
}int main() {atexit(cleanup); // 注册清理函数printf("Program is exiting using exit()\n");exit(0);
}

在这个示例中,exit函数会先调用已注册的清理函数cleanup,然后终止进程。

return退出

return语句是从main函数返回时使用的退出方法。实际上,return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值作为exit的参数。

示例:

#include <stdio.h>int main() {printf("Program is exiting using return\n");return 0;
}

在这个示例中,return语句从main函数返回,并终止进程。

进程等待
进程等待必要性

在进程的生命周期中,子进程退出后,父进程需要通过某种机制获取子进程的退出状态并回收其资源。如果父进程不处理子进程的退出,子进程的退出信息将保留在系统中,导致“僵尸进程”的产生。僵尸进程会占用系统资源,最终可能导致系统性能下降甚至崩溃。

此外,父进程还需要通过进程等待机制来了解子进程的执行情况,例如子进程是否正常退出,退出码是什么,以及是否发生异常终止。这些信息对父进程的后续处理和资源管理非常重要。

进程等待的方法

Linux提供了两种主要的进程等待方法:waitwaitpid

wait方法

wait函数用于等待任一子进程退出,并回收其资源。调用wait时,如果有多个子进程,系统会选择一个已退出的子进程进行处理。如果所有子进程都在运行,wait将阻塞,直到有子进程退出。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>pid_t wait(int *status);

返回值:

  • 成功返回被等待进程的PID
  • 失败返回-1
waitpid方法

waitpid函数提供了更多的控制选项,允许父进程等待指定的子进程或设置非阻塞等待模式。通过参数pidoptions,父进程可以灵活地等待子进程。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>pid_t waitpid(pid_t pid, int *status, int options);

参数:

  • pid:指定等待的子进程PID
    • pid = -1:等待任一子进程,等效于wait
    • pid > 0:等待指定PID的子进程
  • status:子进程退出状态
  • options:等待选项,如WNOHANG(非阻塞等待)

返回值:

  • 成功返回被等待进程的PID
  • 失败返回-1

示例:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;int status;if ((pid = fork()) == -1) {perror("fork");exit(1);}if (pid == 0) {// 子进程执行的代码sleep(5);exit(0);} else {// 父进程等待子进程wait(&status);if (WIFEXITED(status)) {printf("Child exited with code %d\n", WEXITSTATUS(status));} else {printf("Child terminated abnormally\n");}}return 0;
}
获取子进程status

waitwaitpidstatus参数是一个输出型参数,由操作系统填充。通过位图解析子进程的退出状态,可以获取子进程的具体退出信息。

常用的宏定义包括:

  • WIFEXITED(status):如果子进程正常终止,返回非零值。
  • WEXITSTATUS(status):如果WIFEXITED非零,返回子进程的退出码。
  • WIFSIGNALED(status):如果子进程因信号而终止,返回非零值。
  • WTERMSIG(status):如果WIFSIGNALED非零,返回导致子进程终止的信号编号。

示例:

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;int status;if ((pid = fork()) == -1) {perror("fork");exit(1);}if (pid == 0) {// 子进程执行的代码sleep(5);exit(10);} else {// 父进程等待子进程wait(&status);if (WIFEXITED(status)) {printf("Child exited with code %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child terminated by signal %d\n", WTERMSIG(status));} else {printf("Child terminated abnormally\n");}}return 0;
}

在这个示例中,父进程通过wait获取子进程的退出状态,并使用宏定义解析子进程的退出信息。

具体代码实现

通过前面的知识,我们可以实现一个进程的阻塞等待和非阻塞等待的例子。

阻塞等待

阻塞等待意味着父进程会一直等待,直到指定的子进程退出。这种方式适用于父进程必须等待子进程完成任务的场景。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;int status;if ((pid = fork()) == -1) {perror("fork");exit(1);}if (pid == 0) {// 子进程执行的代码printf("Child process is running, pid: %d\n", getpid());sleep(5);exit(0);} else {// 父进程阻塞等待子进程waitpid(pid, &status, 0);if (WIFEXITED(status)) {printf("Child exited with code %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child terminated by signal %d\n", WTERMSIG(status));} else {printf("Child terminated abnormally\n");}}return 0;
}
非阻塞等待

非阻塞等待意味着父进程可以在等待子

进程的同时继续执行其他任务。这种方式适用于父进程需要处理并发任务的场景。

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;int status;if ((pid = fork()) == -1) {perror("fork");exit(1);}if (pid == 0) {// 子进程执行的代码printf("Child process is running, pid: %d\n", getpid());sleep(5);exit(0);} else {// 父进程非阻塞等待子进程do {pid_t ret = waitpid(pid, &status, WNOHANG);if (ret == 0) {printf("Child is still running...\n");sleep(1);} else if (ret == -1) {perror("waitpid");exit(1);}} while (pid != -1 && !WIFEXITED(status) && !WIFSIGNALED(status));if (WIFEXITED(status)) {printf("Child exited with code %d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child terminated by signal %d\n", WTERMSIG(status));} else {printf("Child terminated abnormally\n");}}return 0;
}
进程程序替换
替换原理

在创建子进程后,子进程通常需要执行与父进程不同的任务。为此,子进程可以调用exec函数族以执行另一个程序。当进程调用exec函数时,该进程的用户空间代码和数据被新程序完全替换,进程ID保持不变,从新程序的入口点开始执行。

替换函数

Linux提供了六种以exec开头的函数,用于程序替换。这些函数的主要区别在于参数传递方式和环境变量的处理方式。

#include <unistd.h>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,并设置errno指示错误原因。

函数命名理解
  • l(list):表示参数采用变长参数列表形式
  • v(vector):表示参数采用数组形式
  • p(path):表示文件路径可以通过环境变量PATH搜索
  • e(environment):表示可以指定环境变量
exec调用举例
#include <unistd.h>
#include <stdio.h>int main() {char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};// 直接指定路径execl("/bin/ps", "ps", "-ef", NULL);// 通过环境变量PATH搜索execlp("ps", "ps", "-ef", NULL);// 指定环境变量execle("/bin/ps", "ps", "-ef", NULL, envp);// 参数通过数组传递execv("/bin/ps", argv);// 通过环境变量PATH搜索,参数通过数组传递execvp("ps", argv);// 指定环境变量,参数通过数组传递execve("/bin/ps", argv, envp);return 0;
}

在这个示例中,不同的exec函数被调用以执行ps命令。通过这些示例可以看到,exec函数族提供了丰富的功能,以满足各种需求。

实现一个简易的shell

基于前面介绍的进程创建和程序替换知识,我们可以实现一个简易的shell。shell的主要功能是读取用户输入的命令,解析命令,创建子进程执行命令,并等待子进程结束。

实现思路如下:

  1. 获取命令行输入
  2. 解析命令行参数
  3. 创建子进程(fork
  4. 替换子进程执行用户命令(execvp
  5. 父进程等待子进程退出(waitpid

实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>#define MAX_CMD 1024
char command[MAX_CMD];int get_command() {memset(command, 0x00, MAX_CMD);printf("minishell$ ");fflush(stdout);if (scanf("%[^\n]%*c", command) == 0) {getchar();return -1;}return 0;
}char **parse_command(char *buff) {int argc = 0;static char *argv[32];char *ptr = buff;while (*ptr != '\0') {if (!isspace(*ptr)) {argv[argc++] = ptr;while (!isspace(*ptr) && *ptr != '\0') {ptr++;}} else {while (isspace(*ptr)) {*ptr = '\0';ptr++;}}}argv[argc] = NULL;return argv;
}int execute_command(char *buff) {char **argv = parse_command(buff);if (argv[0] == NULL) {return -1;}int pid = fork();if (pid == 0) {execvp(argv[0], argv);perror("execvp");exit(1);} else {waitpid(pid, NULL, 0);}return 0;
}int main(int argc, char *argv[]) {while (1) {if (get_command() < 0) {continue;}execute_command(command);}return 0;
}

在这个示例中,shell通过循环获取用户输入的命令,解析命令参数,创建子进程执行命令,并等待子进程结束。这种简易的shell实现了基本的命令行解释功能。

总结

本文详细介绍了Linux操作系统中的进程控制,包括进程的创建、终止、等待以及程序替换等内容。
通过对这些知识的学习和实践,可以深入理解Linux进程管理的原理和机制,并能够应用于实际开发中。
嗯,就是这样啦,文章到这里就结束啦,真心感谢你花时间来读。
觉得有点收获的话,不妨给我点个吧!
如果发现文章有啥漏洞或错误的地方,欢迎私信我或者在评论里提醒一声~

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

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

相关文章

咖啡看书休闲时光404错误页面源码

源码介绍 咖啡看书休闲时光404错误页面源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 源码效果 源码下载 咖啡看书…

Java中的多态性:理解和应用面向对象的核心概念

多态性是面向对象编程&#xff08;OOP&#xff09;的核心概念之一&#xff0c;在Java中扮演着至关重要的角色。多态允许对象采取多种形式&#xff0c;增强了程序的灵活性和可扩展性。本文将深入探讨Java中的多态性&#xff0c;包括其定义、工作原理&#xff0c;以及如何在实际编…

QT:协议概述

文章目录 概念帧结构&#xff1a;通信流程 示例&#xff1a;请求帧&#xff1a;响应帧&#xff1a; 概念 帧结构&#xff1a; | SOF (1 byte) | Frame Length (1 byte) | Command (1 byte) | Data Field (N bytes) | Checksum (1 byte) | 通信流程 示例&#xff1a; 请求帧&a…

电解式模具清洗机清洗模具的特点

电解式模具清洗机的特点可以归纳如下&#xff1a; 清洗效果显著&#xff1a; 电解式模具清洗机能够对模具进行深度清洁&#xff0c;有效去除模具表面的污垢、油污、除锈、硫化物、塑胶积碳等&#xff0c;使模具恢复原有的光洁度。清洗前后对比明显&#xff0c;模具更加光亮&am…

守护景区安全:探讨景区视频监控方案的搭建及必要性

据新闻报道&#xff0c;5月25日&#xff0c;安徽黄山景区内发生雷击&#xff0c;闪电击中飞来石景点的护栏&#xff0c;多人被碎石砸中受伤。景区工作人员表示&#xff0c;飞来石附近本就属于雷区&#xff0c;当天曾发过两次雷电预警。 随着旅游业的繁荣发展&#xff0c;越来越…

SpaceX间接「颠覆」了手机?星链如何直连手机通信?

SpaceX 旗下的星链项目推出了一个极具颠覆性的技术——direct to cell&#xff08;DTC&#xff09;&#xff0c;即通过卫星直接与手机建立通信。这项技术无需对手机进行任何改装&#xff0c;大多数普通手机都可以直接接入星链的卫星网络&#xff0c;实现全球范围内的手机信号覆…

MySQL:将空字符串改为NULL

在关系性数据库中Oracle&#xff0c;MySQL&#xff0c;MssSQL中&#xff0c;空字符串()和NULL是两个概念 空字符串顾名思义代表是一个为空的字符串&#xff0c;并不是没有值&#xff0c;而NULL代表没有值或未知值 所以有很多小伙伴&#xff0c;使用IFNULL&#xff0c;ISNULL&…

如何从异步调用中返回响应

想象一下,你打电话给朋友并让他帮你查一些资料。虽然这可能需要一段时间,但你会在电话里等待,直到朋友给你需要的答案。这就是同步调用的行为: function findItem() {var item;while (item_not_found) {// 查找}return item; }var item = findItem(); // 使用 item doSome…

一个 ComfyUI 节点,它使用 的 LLMs 功能对您的输入执行任何操作以进行任何类型的输出-anynode

网址 https://github.com/lks-ai/anynode 一个 ComfyUI 节点&#xff0c;它使用 的 LLMs 功能对您的输入执行任何操作以进行任何类型的输出。

c#对操作系统的时间无法更新?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

Linux 共享内存

Linux 共享内存 1. shmget函数2. shmat函数3. shmdt函数4. shmctl函数5. 注意&#xff1a; 多线程共享进程的地址空间&#xff0c;如果多个线程需要访问同一块内存&#xff0c;用全局变量就可以了。 在多进程中&#xff0c;每个进程的地址空间是独立的&#xff0c;不共享的&am…

152.找出峰值(力扣)

代码解决 class Solution { public:// 函数用于找到山峰元素的索引vector<int> findPeaks(vector<int>& mountain) {vector<int> result; // 用于存储山峰元素的索引// 遍历数组&#xff0c;从第二个元素到倒数第二个元素for(int i 1; i 1 < mount…

【机器学习】MS_MARCO_Web_Search解析说明

MS MARCO Web Search&#xff1a;引领大型模型与信息检索的新纪元 一、引言&#xff1a;大型模型与信息检索的挑战二、MS MARCO Web Search数据集的特点三、MS MARCO Web Search数据集的应用五、结语 在信息爆炸的时代&#xff0c;如何高效、准确地从海量数据中检索出有价值的信…

使用 Spring HATEOAS 开发 REST 服务-浅显的理解

随笔&#xff0c;简单理解 一、restful是什么 1、第一层次&#xff08;Level 0&#xff09;的 Web 服务只是使用 HTTP 作为传输方式&#xff0c;实际上只是远程方法调用&#xff08;RPC&#xff09;的一种具体形式。 SOAP 和 XML-RPC 都属于此类 2、第二层次&#xff08;Lev…

mybatis新增到数据库后返回当前ID

描述 在开发中&#xff0c;插入一条数据并返回当前的ID的场景很多 之前用mybatisPlus自带的api非常简单&#xff0c;调用完save or insert之后再getId即可。 今天使用mybatis的时候也遇到了这个场景&#xff0c;在此记录一下。 解决问题 直接再insert标签里面表明属性 核心…

EQMentor情商导师文心智能体:引领情商提升与人际关系改善的智能导师

目录 一、引言 情商的重要性 EQMentor智能体的诞生背景与目的 二、EQMentor智能体的概述 EQMentor智能体 简述EQMentor情商智能体的核心功能与特点 三、 EQMentor情商导师智能体 智能体的设计理念 智能体的功能特点 智能体的使用举例 四、结语 一、引言 情商的重要…

计算机网络学习笔记——网络层(b站)

目录 网络层概述 网络层提供的两种服务 ①面向连接的虚电路服务 ②无连接的数据报服务 IPv4 路由选择 路由器转发IP数据报 静态路由选择 动态路由选择 路由信息协议RIP 开放最短路径优先OSPF&#xff08;Open Shortest Path First&#xff09; 内部网关协议IGP&…

启智CV机器人,ROS

资料&#xff1a; https://wiki.ros.org/kinetic/Installation/Ubuntu https://blog.csdn.net/qq_44339029/article/details/120579608 装VM。 装ubuntu20.04 desktop.iso系统。 装vm工具&#xff1a; sudo apt update sudo dpkg --configure -a sudo apt-get autoremove o…

(1) 初识QT5

文章目录 Qt Quickdemo信号的命名方式 qml语言一个很重要的概念 qt 模块 Qt Quick Qt Quick是Qt5中⽤户界⾯技术的涵盖。Qt Quick⾃⾝包含了以下⼏种技术&#xff1a; QML-使⽤于⽤户界⾯的标识语⾔JavaScript-动态脚本语⾔Qt C具有⾼度可移植性的C库. 类似HTML语⾔&#xf…

nano机器人2:机械臂的视觉抓取

前言 参考链接: 【机械臂入门教程】机械臂视觉抓取从理论到实战 GRCNN 通过神经网络&#xff0c;先进行模型训练&#xff0c;在进行模型评估。 机械臂逆运动学求解 所有串联型6自由度机械臂均是可解的&#xff0c;但这种解通常只能通过数值解法得到&#xff0c;计算难度大&am…