-
一、概述 Linux 中新建用户的命令是 useradd ,一般系统中这个命令对应的路径都在 PATH 环境变量里,如果直接输入 useradd 不管用的话,就用绝对路径名的方式:/usr/sbin/useradd 。 useradd 新建用户命令只有 root 用户才能执行,我们新建用户kangll ,并设置密码。 二、su 命令介绍及主要用法 首先需要解释下 su 代表什么意思。su 表示 switch user,它提供的功能就是切换用户。 官方释义: su允许运行带有替代用户和组ID的命令。当不带参数调用时,su默认以root身份运行交互式shell。为了向后兼容,su默认不更改当前目录,并且只设置环境变量HOME和SHELL(如果目标用户不是根用户,则加上USER和LOGNAME)。建议始终使用 --login选项(而不是它的快捷方式-),以避免混合环境造成的副作用。 这个版本的su使用PAM进行身份验证、帐户和会话管理。一些其他su实现中的配置选项(例如对wheel组的支持)必须通过PAM进行配置。 su主要是为非特权用户设计的,这是特权用户的推荐解决方案。 2.1 参数- su 的一般使用方法是: su <user_name> 或者 su - <user_name> 两种方法只差了一个字符 -,会有比较大的差异: 如果加入了 - 参数,那么是一种 login-shell 的方式,意思是说切换到另一个用户 <user_name> 之后,当前的 shell 会加载 <user_name> 对应的环境变量和各种设置; 如果没有加入 - 参数,那么是一种 non-login-shell 的方式,意思是说我现在切换到了 <user_name>,但是当前的 shell 还是加载切换之前的那个用户的环境变量以及各种设置。 光解释会比较抽象,我们看一个例子就比较容易理解了。 我们从kangll 用户以 non-login-shell 的方式切换到 root 用户,比较两种用户状态下环境变量中 PWD 的值(su 命令不跟任何 <user_name> ,默认切换到 root 用户): 我们的确是切换到 root用户了,但是 shell 环境中的变量并没有改变,还是用之前 kangll 用户的环境变量。 接着我们从 kangll 用户以 login-shell 的方式切换到 root 用户,同样比较两种用户转台下环境变量中 PWD 的值: 可以看到用 login-shell 的方式切换用户的话,shell 中的环境变量也跟着改变了。 总结:具体使用哪种方式切换用户看个人需求: 如果不想因为切换到另一个用户导致自己在当前用户下的设置不可用,那么用 non-login-shell 的方式; 如果切换用户后,需要用到该用户的各种环境变量(不同用户的环境变量设置一般是不同的),那么使用 login-shell 的方式。 2.2 切换到指定用户 前面已经介绍了,如果 su 命令后面不跟任何 <user_name>,那么默认是切换到 root 用户: 如下我们可以从 mysql 用户切换到 kangll 用户: 2.3 参数-c 前面的方法中,我们都是先切换到另一个用户(root 或者 kangll),在哪个用户的状态下执行命令,最后输入 exit 返回到当前用户。 还有一种方式是:不需要先切换用户再执行命令,可以直接在当前用户下,以另一个用户的方式执行命令,执行结束后就返回当前用户。这就得用到 -c 参数。 具体使用方法是: su - -c "指令串" # 以 root 的方式执行 "指令串" 看个例子: 这种执行方式一般是我们想以某个用户启动一个特定的进程,比如下面后台启动hive进程: su - hive -c 'env HADOOP_HOME=//usr/hdp/2.6.4.0-91/hadoop nohup hive --service metastore > /var/log/hive/hive.out 2> /var/log/hive/hive.log &' 三、sudo 命令介绍及主要用法 sudo 的英文全称是 super user do,即以超级用户(root 用户)的方式执行命令。这里的 sudo 和之前 su 表示的 switch user 是不同的,这点需要注意,很容易搞混。 我们先介绍 sudo 命令能做什么事情。 3.1 主要用法 我们在 Linux 中经常会碰到 Permission denied 这种情况,比如以 kangll 用户的身份查看 /etc/profile 的内容。因为这个文件的内容是只有 root 用户能查看的。 那如果我们想要查看怎么办呢?这时候就可以使用 sudo : 需要输入当前这个用户的密码,本例中需要输入 knagll 用户的登录密码。 两次相邻的 sudo 操作,如果间隔在 5min 之内,第二次输入 sudo 不需要重新输入密码;如果超过 5min,那么再输入 sudo 时,又需要输入密码。所以一个比较省事的方法是设置 sudo 操作不需要密码,后面我们将介绍如何设置。 sudo 除了以 root 用户的权限执行命令外,还有其它几个用法,这里做简单介绍。 切换到 root 用户: sudo su - 这种方式也能以 login-shell 的方式切换到 root 用户,但是它和 su - 方法是由区别的: 前者输入 sudo su - 后,需要提供当前用户的登录密码,也就是 kangll 用户的密码; 后者输入 su - 后,是切换到root用户,需要提供 root 用户的登录密码。 还有一个命令: sudo -i 这个命令和 sudo su - 效果一致,也是切换到 root 用户,也是需要提供当前用户(ubuntu 用户)的登录密码。 我们现在切换到 winner 用户,尝试显示 /etc/shadow 文件的内容: 我们会看到错误提示信息,并无法查看 /etc/shadow 的内容,这就需要为普通用户设置sudo权限。 3.2 sudo 工作原理 一个用户能否使用 sudo 命令,取决于 /etc/sudoers 文件的设置。 从上节我们已经看到,kangll 用户可以正常使用 sudo ,这是因为 /etc/sudoers 文件里做了设置。 /etc/sudoers 是一个文本文件,但是因其有特定的语法,我们一般不直接用 vim 或者 vi 来编辑它,需要用 visudo 这个命令。输入这个命令之后就能直接编辑文件 /etc/sudoers 。需要注意的是,只有 root 用户有权限使用 visudo 命令。 我们先来看下输入 visudo 命令后显示的内容。 [root@windp-aio ~]# visudo 输出: ## Allow root to run any commands anywhere root ALL=(ALL) ALL ## Allows members of the 'sys' group to run networking, software, ## service management apps and more. # %sys ALL = NETWORKING, SOFTWARE, SERVICES, STORAGE, DELEGATING, PROCESSES, LOCATE, DRIVERS ## Allows people in group wheel to run all commands %wheel ALL=(ALL) ALL ## Same thing without a password # %wheel ALL=(ALL) NOPASSWD: ALL kangll ALL=(ALL) ALL ## Allows members of the users group to mount and unmount the ## cdrom as root # %users ALL=/sbin/mount /mnt/cdrom, /sbin/umount /mnt/cdrom ## Allows members of the users group to shutdown this system # %users localhost=/sbin/shutdown -h now ## Read drop-in files from /etc/sudoers.d (the # here does not mean a comment) #includedir /etc/sudoers.d 解释下每一行的格式: 第一个表示用户名,如 root 、kangll 等; 接下来等号左边的 ALL 表示允许从任何主机登录当前的用户账户; 等号右边的 ALL 表示:这一行行首用户可以切换到系统中任何一个其它用户; 行尾的 ALL 表示:当前行首的用户,能以 root 用户的身份下达什么命令,ALL 表示可以下达任何命令。 我们还注意到 kangll 对应的那一行有个 NOPASSWD 关键字,这就是表明 kangll 这个用户在请求 sudo 时不需要输入密码,到这里就解释了前面的问题。 同时这个文件里并没有 winner 对应的行,这也就解释了为什么 winner 无法使用 sudo 命令。 接下来,我们尝试将 winner 添加到 /etc/sudoers 文件中,使 winner 也能使用 sudo 命令。我们在最后一行添加: # winner 使用 sudo 不需要提供密码 ,且后面对操作目录做了限制 winner ALL=(ALL) NOPASSWD:ALL,!/usr/bin/su,!/usr/bin/chattr,!/usr/sbin/init,!/usr/sbin/reboot,!/sbin/reboot,!/usr/sbin/shutdown,!/usr/bin/passwd,!/bin/bash,!/bin/sh winner表示的是开发用户 NOPASSWD:ALL表示sudo命令可以免密使用 !/usr/bin/su表示禁止使用sudo su - !/usr/sbin/iptables表示禁止使用reboot !/bin/chattr表示禁止使用chattr 如上特别注意命令需要加上绝对路径。 接下来我们再在 winner 账户下执行 sudo : 可以看到,现在已经可以使用 sudo 了,限制的命令也起作用了。 四、二者的差异对比 我们可以看到: 使用 su - ,提供 root 账户的密码,可以切换到 root 用户; 使用 sudo su - ,提供当前用户的密码,也可以切换到 root 用户 两种方式的差异也显而易见:如果我们的 Linux 系统有很多用户需要使用的话,前者要求所有用户都知道 root 用户的密码,这显然是非常危险的;后者是不需要暴露 root 账户密码的,用户只需要输入自己的账户密码就可以,而且哪些用户可以切换到 root,这完全是受 root 控制的(root 通过设置 /etc/sudoers 实现的),这样系统就安全很多了。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_35995514/article/details/143199093
-
1 epoll的作用和定位 之前提过的多路转接方案select和poll 都有致命缺点:底层都是暴力的遍历,效率不高! 对此,诞生出了epoll这个更好的方案! 按照 man 手册的说法: 是为处理大批量句柄而作了改进的 poll。它是在 2.5.44 内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)。它几乎具备了之前所说的一切优点, 被公认为 Linux2.6 下性能最好的多路 I/O 就绪通知方法. 2 epoll 的接口 epoll的相关接口有三个: epoll_create EPOLL_CREATE(2) Linux Programmer's Manual EPOLL_CREATE(2) NAME epoll_create, epoll_create1 - open an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_create(int size); int epoll_create1(int flags); epoll_create接口只有一个参数,其功能是在内核创建一个epoll模型!这个模型我们后面详细谈。这个size我们只有设置为一个大于零的数即可。创建成功之后会给我们返回一个文件描述符,现在我们还理解不了,后续讲解。 epoll_ctl EPOLL_CTL(2) Linux Programmer's Manual EPOLL_CTL(2) NAME epoll_ctl - control interface for an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); DESCRIPTION This system call is used to add, modify, or remove entries in the interest list of the epoll(7) instance referred to by the file descriptor epfd. It requests that the operation op be performed for the target file descriptor, fd. epoll_ctl有四个参数: int epfd:这个就是通过epoll_create获得的文件描述符 int op:这个是操作选项,我们这个函数共用三种选项:EPOLL_CTL_ADD增加 EPOLL_CTL_MOD 修改EPOLL_CTL_DEL删除。 int fd:对这个文件描述符进行操作。 struct epoll_event * event:这时一个结构体,类似struct pollfd,但内部更加复杂: typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 其中的events位图就可以设置读事件,写事件…注意这里没有返回事件! epoll_wait EPOLL_WAIT(2) Linux Programmer's Manual EPOLL_WAIT(2) NAME epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask); DESCRIPTION The epoll_wait() system call waits for events on the epoll(7) instance referred to by the file descriptor epfd. The buffer pointed to by events is used to return in‐ formation from the ready list about file descriptors in the interest list that have some events available. Up to maxevents are returned by epoll_wait(). The max‐ events argument must be greater than zero. epoll_wait有四个参数: int epfd:这个就是通过epoll_create获得的文件描述符。 *struct epoll_event events :这是一个数组,向内核输入一个缓冲区,想让内核提供这个数组将就绪事件返回来! ** int maxevents**:数组的元素个数。 int timeout:等价于poll接口的timeout,以毫秒为单位! 返回值等价于poll! 总而言之:epoll将传入与传出分成了两个接口来进行! 3 epoll工作原理 对于epoll更深入的理解我们需要从底层进行讲解: 数据到达主机时,数据首先会到达物理层,那么操作系统如何知道网卡里有数据呢?通过硬件中断!通过针脚中断,就可以通知操作系统!从而数据链路层从网络层读取数据! 当我们使用epoll时,系统内部会建立一个红黑树,这个红黑树创建时是空树。红黑树的节点字段主要存在:文件描述符fd , 事件位图 events ,左右指针,节点颜色...,这个树标识了用户想让OS关心的文件操作符fd以及其对应事件!epoll_ctl接口中的op就是对应的增添修改删除红黑树节点!注意:这个红黑树的键值是fd! 其中还有一个就绪队列,这是一个双向链表,每个节点与红黑树中的节点类似。当网卡中有数据了,网卡通过硬件中断把数据交给网络协议栈。OS可以知道每个文件描述符对应的输入输出缓冲区状态,当回红黑树节点对应fd的EPOLLIN事件等就绪,那么OS就把这个fd的事件放入就绪队列。这个就绪队列就是储存就绪事件的数据结构,当用户调用epoll_wait时,就通过就绪队列进行检测哪个fd对应事件就绪了!将事件依次严格按照顺序放入struct epoll_event *events数组中! 这个检测就绪事件的算法的时间复杂度就是O(1)!只需要判断就绪队列是否为空就可以!而将就绪事件获取的时间复杂度是O(n)! 这就是epoll模型!!! 而这个epoll模型是可以打开多个的,就和打开多个文件一样。当我们打开多个epoll模型时,那么操作系统如何管理这些epoll模型呢? 在内核中有一个eventpoll,这个是描述epoll模型的结构体,其中就有rbr红黑树与rdllist就绪队列。那为什么创建epoll模型之后会返回一个文件描述符呢? 在内核中有无数个task_struct进程结构体,每个进程都有一张文件描述符表struct files_struct,这个表的元素就指向文件结构体struct file。文件结构体中就有一个指针指向epoll模型。那么在进程中想要找到epoll模型就可以通过文件描述符表找到epoll模型! 我们来谈一个十分巧妙的设计。在epoll模型中,存在红黑树和就绪队列。每个节点都有对应的文件描述符。在之前所学的数据结构中,我们每个数据结构的节点都是独属于自身的,比如二叉树的节点不可能是链表的节点。 但是在epoll模型中,一个节点是可以属于多个数据结构的!我们来看是如何实现的: 首先,有这样一个链表节点listnode,其中只包含左右指针。 然后在task_struct中,就可以存在listnode link,那么每一个task_struct就可以通过这个link进行连接起来的。 但是,这个指向的只是下一个task_struct结构体中的link,那么怎么才能访问task_struct全部的数据呢? 可以先计算这个link在task_struct的偏移量,通过将0地址强制类型转换,得到里面link的地址,就知道了偏移量!然后通过task_struct中link里的指针减去偏移量,我们就得到了task_struct的起始地址,再进行类型转换我们就得到了task_struct! 同样的,task_struct还可以存在二叉树节点link2 , 队列节点link3,就都可以通过这种方式进行链接,并且是一个节点属于了多个数据结构中!!! 这是十分巧妙的设计!!!而epoll模型中的epitem结构体就是这样设计的!一个节点既属于红黑树,也属于就绪队列! 其中epitem还有一个status变量,表示其是否被激活。可以判断是否在红黑树或者就绪队列中! 下面我们开始编写v1版本的epollserver 4 实现epollserverV1 下面我们来实现epollserver: 成员变量需要以下: 端口号_port :用于创建listen套接字 套接字socket :_listensock监听套接字,使用TCP进行通信。 文件描述符_epfd :epoll模型的文件操作符,是使用epoll系列接口的必要参数。 epoll_event revs[] 数组:从epoll模型中获取就绪事件的结构体数组。 根据成员变量,进行构造,创建套接字,创建epoll模型。 初始化函数中,建立struct epoll_event ev设置其中的 fd 与events位图;先将_listensock套接字fd添加到epoll中 通过epoll_ctl进行ADD操作。 #pragma once #include <string> #include <iostream> #include <memory> #include <sys/epoll.h> #include "Log.hpp" #include "Socket.hpp" using namespace log_ns; using namespace socket_ns; class EpollServer { private: const static int gnum = 1024; const static int size = 128; public: EpollServer(uint16_t port) : _port(port), _listensock(std::make_unique<TcpSocket>()) { // 建立监听套接字 _listensock->BuildListenSocket(port); // 建立epoll模型 _epollfd = ::epoll_create(size); if (_epollfd < 0) { // 创建失败 LOG(FATAL, "epoll_create failed!\n"); exit(1); } } void InitServer() { // 将监听套接字放入epoll模型 struct epoll_event ev; ev.data.fd = _listensock->GetSockfd(); ev.events = EPOLLIN; // 放入 int n = ::epoll_ctl(_epollfd, EPOLL_CTL_ADD, _listensock->GetSockfd(), &ev); // 根据返回值判断 if (n < 0) { // 发生错误 LOG(FATAL, "epoll_ctl failed ,errno :%d", errno); exit(1); } } void Accepter() { } void HandlerIO(int fd) { // 普通fdIO 就绪 } void HandlerEvent(int n) { } void Loop() { } ~EpollServer() { // 关闭epoll模型 if (_epollfd > 0) close(_epollfd); // 关闭监听套接字 _listensock->Close(); } private: // 端口号 uint16_t _port; // 套接字 std::unique_ptr<Socket> _listensock; // epoll模型描述符 int _epollfd; // 文件描述符 struct epoll_event revs[gnum]; }; Loop 循环函数,设置timeout 调用epoll_wait接口进行等待事件就绪 ,将就绪的事件放入到revs数组中。根据返回值进行判断结果: void Loop() { int timeout = 2000; while (true) { // 进行等待 int n = ::epoll_wait(_epollfd, revs, gnum, timeout); // 判断结果 switch (n) { case 0: LOG(INFO, "epoll timeout...\n"); break; case -1: LOG(ERROR, "epoll error\n"); break; default: LOG(INFO, "haved event happened! , n :%d\n", n); // 处理事件 HandlerEvent(n); break; } } } HandlerEvent处理事件,将数组中的n个事件全部处理遍历一遍, 根据就绪的文件描述符种类进行区分判断 (设计一个简单的接口可以通过事件级返回事件种类);读事件就绪 我们进行处理 _listensock套接字事件获取连接 Accepter 将新的fd加入到epoll模型 打印客户端信息 普通fd 事件HandlerIO 进行读取recv ;读取失败的话要从epoll删除后再close ,处理后Send回去。 std::string PrintEvent(uint32_t revents) { std::string ret; if (revents & EPOLLIN) ret += "EPOLLIN"; if (revents & EPOLLOUT) ret += "| EPOLLOUT"; return ret; } void Accepter() { // 获取_listensock的新fd InetAddr addr; int sockfd = _listensock->Accepter(&addr); if (sockfd < 0) { LOG(ERROR, "Accepter error\n"); exit(1); } // 成功获取连接 LOG(INFO, "成功获取连接 ,客户端: %s\n", addr.AddrStr().c_str()); // 将连接添加到epoll模型中 struct epoll_event ev; ev.data.fd = sockfd; ev.events = EPOLLIN; int n = ::epoll_ctl(_epollfd, EPOLL_CTL_ADD, sockfd, &ev); // 根据返回值判断 if (n < 0) { // 发生错误 LOG(FATAL, "epoll_ctl failed ,errno :%d", errno); exit(1); } } void HandlerIO(int fd) { // 普通fdIO 就绪 char buffer[4096]; int n = ::recv(fd, buffer, sizeof(buffer), 0); if (n > 0) { // 读取到了数据 buffer[n] = 0; std::string echo_str = "[client say]#"; echo_str += buffer; std::cout << echo_str << std::endl; // 返回一个报文 std::string content = "<html><body><h1>hello bite</h1></body></html>
-
1. ls 指令 语法: ls [选项][目录或文件] 功能:对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息 ls -l 命令与 ll 命令所发出的指令作用相同,因为 ll 指令是系统用 ls -l 指令的默认使用 'alias' 指令取的一个别名 常用选项: -a 列出目录下的所有文件,包括以 . 开头的隐含文件 -d 将目录象文件一样显示,而不是显示其下的文件。 如:ls –d 指定目录 -i 输出文件的 i 节点的索引信息。 如 ls –ai 指定文件 -k 以 k 字节的形式表示文件的大小。ls –alk 指定文件 -l 列出文件的详细信息 -n 用数字的 UID,GID 代替名称。 (介绍 UID, GID) -F 在每个文件名后附上一个字符以说明该文件的类型: “*”表示可执行的普通文件;“/”表示目录;“@”表示符号链接;“|”表示FIFOs;“=”表示套接字(sockets)(目录类型识别);以 . 开头的隐含文件 -r 对目录反向排序 -t 以时间排序 -s 在l文件名后输出该文件的大小。(大小排序,如何找到目录下最大的文件) -R 列出所有子目录下的文件(递归) -1 一行只输出一个文件 2. pwd指令 语法: pwd 功能:显示用户当前所在的目录 注意:在Windows操作系统下是以 '\'作为路径分隔符,在Linux操作系统下是以'/'作为路径分隔符 3. cd 指令 Linux系统中,磁盘上的文件和目录被组成一棵目录树,每个节点都是目录或文件 语法:cd 目录名 功能:改变工作目录。将当前工作目录改变到指定的目录下 举例: cd . : 返回当前目录 cd .. : 返回上级目录 cd /home/litao/linux/ : 绝对路径 cd ../day02/ : 相对路径 cd ~:进入用户家目 cd -:返回最近访问目录 root@hcss-ecs-78b3:/# cd /root root@hcss-ecs-78b3:~# pwd /root root@hcss-ecs-78b3:~# cd . root@hcss-ecs-78b3:~# pwd /root root@hcss-ecs-78b3:~# cd .. root@hcss-ecs-78b3:/# pwd / root@hcss-ecs-78b3:/# 切换文件路径,格式为 cd 空格 目标路径 任何一个目录包括根目录,系统都会默认自带 . 和 .. 目录,第一个'/'代表Linux中的根目录,没有上级目录返回,这里我们要注意Linux的文件结构是一个以 '/' 为根目录的多叉树 4. touch指令 语法:touch [选项]... 文件... 功能:touch命令参数可更改文档或目录的日期时间,包括存取时间和更改时间,或者新建一个不存在的文件 常用选项: -a 或--time=atime或--time=access或--time=use只更改存取时间 -c 或--no-create 不建立任何文档 -d 使用指定的日期时间,而非现在的时间 -f 此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题 -m 或--time=mtime或--time=modify 只更改变动时间 -r 把指定文档或目录的日期时间,统统设成和参考文档或目录的日期时间相同 -t 使用指定的日期时间,而非现在的时间 root@hcss-ecs-78b3:~# touch hello.txt root@hcss-ecs-78b3:~# pwd /root root@hcss-ecs-78b3:~# ls hello.txt 5. mkdir指令 语法:mkdir [选项] kiana 功能:在当前目录下创建一个名为 “kiana”的目录 常用选项: -p, kiana 可以是一个路径名称 此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录 root@hcss-ecs-78b3:~# mkdir kiana root@hcss-ecs-78b3:~# ll total 116 drwx------ 12 root root 4096 Oct 15 11:03 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 21926 Jul 22 2023 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* drwxr-xr-x 2 root root 4096 Oct 15 11:03 kiana/ -rw------- 1 root root 20 Oct 15 10:11 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3:~# mkdir命令创建连续的文件 root@hcss-ecs-78b3:~# mkdir -p a/b/c root@hcss-ecs-78b3:~# ll total 120 drwx------ 13 root root 4096 Oct 15 11:05 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwxr-xr-x 3 root root 4096 Oct 15 11:05 a/ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 21926 Jul 22 2023 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* drwxr-xr-x 2 root root 4096 Oct 15 11:03 kiana/ -rw------- 1 root root 20 Oct 15 10:11 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3:~# 使用tree命令展示目录下树形结构的文件 root@hcss-ecs-78b3:~# tree kiana kiana 0 directories, 0 files root@hcss-ecs-78b3:~# tree a a └── b └── c 2 directories, 0 files root@hcss-ecs-78b3:~# 6. which指令 查看命令所在的路径,命令其实就是可执行文件,也就相当于我们平常写的C/C++程序 root@hcss-ecs-78b3:~# root@hcss-ecs-78b3:~# which tree /usr/bin/tree root@hcss-ecs-78b3:~# which pwd /usr/bin/pwd root@hcss-ecs-78b3:~# which ls /usr/bin/ls root@hcss-ecs-78b3:~# 7. alias指令 给命令起别名,比如 ll 就是 ls -l 的别名,二者执行后结果是一样的,当然这里alias起的别名在重启程序后就会自动失效 如: 给pwd命令取别名叫做kiana root@hcss-ecs-78b3:alias kiana=pwd 8. rm指令 rm命令可以同时删除文件或目录 语法:rm [-f-i-r-v][dirName/dir] 适用对象:所有使用者 功能:删除文件或目录 常用选项: -f 即使文件属性为只读(即写保护),亦直接删除 -i 删除前逐一询问确认 -r 删除目录及其下所有文件 root@hcss-ecs-78b3:~# ll total 120 drwx------ 13 root root 4096 Oct 15 11:05 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwxr-xr-x 3 root root 4096 Oct 15 11:05 a/ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 21926 Jul 22 2023 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* drwxr-xr-x 2 root root 4096 Oct 15 11:03 kiana/ -rw------- 1 root root 20 Oct 15 10:11 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3:~# rm -rf kiana root@hcss-ecs-78b3:~# ll total 116 drwx------ 12 root root 4096 Oct 15 11:22 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwxr-xr-x 3 root root 4096 Oct 15 11:05 a/ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 21926 Jul 22 2023 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* -rw------- 1 root root 20 Oct 15 10:11 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3: 9. man指令 查找系统手册返回所查找命令的实质 语法: man [选项] 命令 常用选项: -k 根据关键字搜索联机帮助 num 只在第num章节找 -a 将所有章节的都显示出来,比如 man printf 它缺省从第一章开始搜索,知道就停止,用a选项,当按下q退出,他会继续往后面搜索,直到所有章节都搜索完毕 解释一下,面手册分为8章: 1 是普通的命令 2 是系统调用,如open,write之类的(通过这个,至少可以很方便的查到调用这个函数,需要加什么头文件) 3 是库函数,如printf,fread4是特殊文件,也就是/dev下的各种设备文件 5 是指文件的格式,比如passwd, 就会说明这个文件中各个字段的含义 6 是给游戏留的,由各个游戏自己定义 7 是附件还有一些变量,比如向environ这种全局变量在这里就有说明 8 是系统管理用的命令,这些命令只能由root使用,如ifconfig root@hcss-ecs-78b3:~# man man root@hcss-ecs-78b3:~# man pwd root@hcss-ecs-78b3:~# man ll 10. cp指令 语法:cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录,则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的目的地并非一个已存在的目录,则会出现错误信息 常用选项: -f 或 --force 强行复制文件或目录, 不论目的文件或目录是否已经存在 -i 或 --interactive 覆盖文件之前先询问用户 -r递归处理,将指定目录下的文件与子目录一并处理。若源文件或目录的形态,不属于目录或符号链接,则一律视为普通文件处理 -R 或 --recursive递归处理,将指定目录下的文件及子目录一并处理 drwx------ 12 root root 4096 Oct 15 11:22 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwxr-xr-x 3 root root 4096 Oct 15 11:05 a/ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 21926 Jul 22 2023 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* -rw------- 1 root root 20 Oct 15 10:11 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3:~# root@hcss-ecs-78b3:~# man man root@hcss-ecs-78b3:~# root@hcss-ecs-78b3:~# cp .wget-hsts get-docker.sh root@hcss-ecs-78b3:~# ll total 96 drwx------ 12 root root 4096 Oct 15 11:27 ./ drwxr-xr-x 24 root root 4096 Oct 15 09:32 ../ drwxr-xr-x 3 root root 4096 Oct 15 11:05 a/ drwx------ 3 root root 4096 Jul 22 2023 .ansible/ drwxr-xr-x 2 root root 4096 Jul 22 2023 .ansible_async/ -rw-r--r-- 1 root root 298 Oct 15 10:15 .bash_history -rw-r--r-- 1 root root 3144 Jul 22 2023 .bashrc drwx------ 2 root root 4096 Feb 10 2023 .cache/ drwxr-xr-x 7 root root 4096 Jul 22 2023 docker-library/ -rw-r--r-- 1 root root 173 Oct 15 11:32 get-docker.sh -rw-r--r-- 1 root root 1326 Jul 21 2023 githubclone.sh -rw------- 1 root root 0 Feb 10 2023 .history -rwxr-xr-x 1 root root 15353 Jul 22 2023 HSSInstall* -rw------- 1 root root 20 Oct 15 11:27 .lesshst drwxr-xr-x 8 root root 4096 Jul 22 2023 library/ drwxr-xr-x 3 root root 4096 Oct 15 09:29 .local/ -rw-r--r-- 1 root root 161 Jul 9 2019 .profile drwx------ 4 root root 4096 Jul 22 2023 snap/ drwx------ 2 root root 4096 Oct 11 19:41 .ssh/ drwxr-xr-x 9 root root 4096 Jul 22 2023 stackhub/ -rw-r--r-- 1 root root 173 Jul 22 2023 .wget-hsts -rw------- 1 root root 177 Oct 15 11:03 .Xauthority root@hcss-ecs-78b3:~# 11. mv指令 mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录 语法: mv [选项] 源文件或目录 目标文件或目录 功能: 1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的目录中 2. 当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它将所给的源文件或目录重命名为给定的目标文件名 3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至目标目录中 常用选项: -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖! 理论杂谈 12. cat指令 语法:cat [选项][文件] 功能: 查看目标文件的内容 常用选项: -b 对非空输出行编号 -n 对输出的所有行编号 -s 不输出多行空行 使用nano记事本写入数据到1.c文件中 使用cat打印文件中的内容 cat正向打印,tac反向打印 echo只打印后面跟着的字符 重定向操作 1.输出重定向 > 语法: echo "指定字符串" > 目标写入文件 特点: 1.当目标写入文件不存在就直接自动创建一个 2.每一次写入操作都会替代原来文件中的内容,先清空后写入 所以在没有字符串时可以使用该输出重定向来只新建文件或者清空文件 2.追加重定向 >> 语法:echo "字符串" >> 目标文件 特点: 1.写入文件时不会清空原文件内容而是直接添加进目标写入文件中 3. 输入重定向 < cat 默认从键盘读取输入内容,而输入重定向就是改变输入路径,可以是一个文件直接被cat读取后显示在显示屏上 13. more指令 语法:more [选项][文件] 功能:more命令,功能类似 cat 常用选项: -n 对输出的所有行编号 q 退出more [atong@LiWenTong ~]$ ls -l / | more total 162 drwxr-xr-x 2 root root 4096 Apr 25 05:39 bin drwxr-xr-x 4 root root 1024 Apr 25 04:11boot drwxr-xr-x 9 root root 3820 May 4 23:20 dev drwxr-xr-x 84 root root 4096 May 5 00:37 etc 14. less指令 less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极其强大 less 的用法比起 more 更加的有弹性。在 more 的时候,我们并没有办法向前面翻, 只能往后面看 但若使用了 less 时,就可以使用 [pageup][pagedown] 等按键的功能来往前往后翻看文件,更容易用来查看一个文件的内容! 除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜 语法: less [参数] 文件 功能: less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前不会加载整个文件 -i 忽略搜索时的大小写 -N 显示每行的行号 /字符串:向下搜索“字符串”的功能 ?字符串:向上搜索“字符串”的功能 n:重复前一个搜索(与 / 或 ? 有关) N:反向重复前一个搜索(与 / 或 ? 有关) q:quit 15. head指令 head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾 语法: head [参数]... [文件]... 功能: head 用来显示档案的开头至标准输出中,默认head命令打印其相应文件的开头10行 选项: -n<行数> 显示的行数 16. tail指令 tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新,使你看到最新的文件内容 语法: tail[必要参数][选择参数][文件] 功能: 用于显示指定文件末尾内容,不指定文件时,作为输入信息进行处理。常用查看日志文件 选项: -f 循环读取 -n<行数> 显示行数 扩展:管道 | 可以类比条件概率中的"|",这里指在"|"前面的操作指令完成后再进行"|"之后的操作指令 17. 时间相关的指令 17.1. date显示 date 指定格式显示时间: date +%Y:%m:%d date 用法:date [OPTION]... [+FORMAT] 1.在显示方面,使用者可以设定欲显示的格式,格式设定为一个加号后接数个标记,其中常用的标记列表如下 %H : 小时(00..23) %M : 分钟(00..59) %S : 秒(00..61) %X : 相当于 %H:%M:%S %d : 日 (01..31) %m : 月份 (01..12) %Y : 完整年份 (0000..9999) %F : 相当于 %Y-%m-%d 2.在设定时间方面 date -s //设置当前时间,只有root权限才能设置,其他只能查看 date -s 20080523 //设置成20080523,这样会把具体时间设置成空00:00:00 date -s 01:01:01 //设置具体时间,不会对日期做更改 date -s “01:01:01 2008-05-23″ //这样可以设置全部时间 date -s “01:01:01 20080523″ //这样可以设置全部时间 date -s “2008-05-23 01:01:01″ //这样可以设置全部时间 date -s “20080523 01:01:01″ //这样可以设置全部时间 3.时间戳 时间->时间戳:date +%s 时间戳->时间:date -d@1508749502 Unix时间戳(英文为Unix epoch, Unix time, POSIX time 或 Unix timestamp)是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数,不考虑闰秒 18. Cal指令 cal命令可以用来显示公历(阳历)日历。公历是现在国际通用的历法,又称格列历,通称阳历。“阳历”又名“太阳历”,系以地球绕行太阳一周为一年,为西方各国所通用,故又名“西历” 命令格式: cal [参数][月份][年份] 功能: 用于查看日历等时间信息,如只有一个参数,则表示年份(1-9999),如有两个参数,则表示月份和年份 常用选项: -3 显示系统前一个月,当前月,下一个月的月历 -j 显示在当年中的第几天(一年日期按天算,从1月1号算起,默认显示当前月在一年中的天数) -y 显示当前年份的日历 19. find 查找指令 Linux下find命令在目录结构中搜索文件,并执行指定的操作 Linux下find命令提供了相当多的查找条件,功能很强大。由于find具有强大的功能,所以它的选项也很 多,其中大部分选项都值得我们花时间来了解一下 即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系 统可能会花费很长的时间(这里是指30G字节以上的文件系统) 语法: find pathname -options 功能: 用于在文件树种查找文件,并作出相应的处理(可能访问磁盘) 常用选项: -name 按照文件名查找文件 20. grep指令 语法: grep [选项] 搜寻字符串 文件 功能: 在文件中搜索字符串,将找到的行打印出来 常用选项: -i :忽略大小写的不同,所以大小写视为相同 -n :顺便输出行号 -v :反向选择,亦即显示出没有 '搜寻字符串' 内容的那一行 21 .zip/unzip指令 语法: zip 压缩文件.zip 目录或文件 功能: 将目录或文件压缩成zip格式 常用选项: -r 递 归处理,将指定目录下的所有文件和子目录一并处理 举例: 将test2目录压缩:zip test2.zip test2/* 解压到指定目录tmp:unzip test2.zip -d /tmp zip --version :确认系统有没有装zip 如图,我们压缩了一个压缩包day4.zip,将这个压缩包传递给other,但是当我们打开之后发现day4.zip下的文件并没有一起传送过去,只传了一个空文件夹day4.zip,解决方式就是要加上一个 ‘-r’ zip -r day4.zip day4 解压到指定目录下需要添加 -d 将linux中的压缩包传给windows系统可以使用 sz 指令直接回车就会出现以下界面 选择桌面传完之后就能在桌面上看到文件了 将windows中的压缩包传给linux系统可以使用 rz 指令直接回车就会出现以下界面 22. tar指令:打包/解包,不打开它,直接看内容 tar [-cxtzjvf] 文件与目录 .... 参数: -c :建立一个压缩文件的参数指令(create 的意思) -x :解开一个压缩文件的参数指令! -t :查看 tarfile 里面的文件! -z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩? -j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩? -v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程! -f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数! -C : 解压到指定目录 Linux和Linux之间的压缩包互相传输 23. uname指令 功能: uname用来获取电脑和操作系统的相关信息 语法:uname [选项] 补充说明:uname可显示linux主机所用的操作系统的版本、硬件的名称等基本信息 常用选项: -a或–all 详细输出所有信息,依次为内核名称,主机名,内核版本号,内核版本,硬件名,处理器类 型,硬件平台类型,操作系统名称 收尾杂谈 显示出100条“hello+数字”的信息命令 cnt=0; while [ $cnt -le 100 ]; do echo "hello $cnt"; let cnt++;done 在当前目录下建立100个原文件命令 cnt=0; while [ $cnt -le 100 ]; do touch code${cnt}.c; let cnt++;done echo加上 | bc 就可以直接算出答案 ctrl + r:搜索历史命令 ctrl + d:退出当前用户 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/hedhjd/article/details/142934064
-
一、认识硬件——磁盘 1.1 物理构成 磁盘是唯一的一个机械设备,也是一个外设! 以前的老式电脑上装的就是机械磁盘,现在由于用户对使用计算机的速度要求越来越高,现在我们普通人使用的电脑基本上都是用的SSD固态硬盘,SSD固态硬盘并没有像机械磁盘那样的机械运动,读写速度更快,且具有体积小、低功耗、耐用性好、无噪音等特点!且未来还有很大的研究空间!所以在桌面领域几乎取代了机械磁盘! 但是企业级存储更倾向于使用机械硬盘,由于其成本低、容量大的特点更有利于进行大规模的数据存储,所以他其实很难被淘汰! 1、写入磁盘工作原理:二进制序列会通过磁头的充放电(任何硬件都只认识二进制序列,因为本质上是用线连接的),将数据写到盘片上。 2、 一些特点: (1)我们的计算机内部的信息流动是以电子或者光电信号的形式传递(非常快),而磁盘是机械运动相比之下速度很慢!! (2)盘片高速旋转,磁头左右转动,磁头是一面一个且和盘面不接触! (3)磁盘在设计的时候必须保证无尘环境且密封完好,因为有灰尘的话可能会导致盘面刮花造成数据丢失。 (4)内存是掉电易失存储介质,盘片是永久性存储介质。 3、注:磁盘是有寿命的,大公司磁盘快报废的时候并不敢直接把磁盘给丢掉,因为里面存储了大量的用户数据(磁盘密封性好且不易被销毁),所以相关的安全部门对大公司的磁盘销毁工作时有严格的要求的! 1.2 存储构成 磁头的左右摆动——>定位磁道(柱面) 磁头不动的时候,盘片的旋转——>定位扇区 所以磁盘被访问的最基本单位是扇区 ——> 512字节/4KB ——>因此我们可以把磁盘看做由无数个扇区构成的存储介质 ! ——>所以我们要把数据存到磁盘,首先就是定位一个扇区:(1)先找到哪一面(定位磁头)(2)哪一个磁道(3)哪一个扇区 1.3 逻辑抽象 我们把假设把磁带摊开,从逻辑上我们就可以把他看做是线性的!我们就可以用一个数组把扇区组织起来,每个下标对应着一个扇区(我们把逻辑扇区地址叫做LBA地址)! 问题1:那么我们如何通过下标找到我们要写入的扇区呢?? 问题2: 为什么扇区大小不均匀的但是LBA地址是均匀?? ——>因为磁道是从中间向外辐射的,所以里面的磁道有多少个扇区,外面的磁道就有多少个扇区,只不过里面磁道的扇区会小一点(扇区大小不均匀) 但其实可以通过调整密度来变得均匀(里面的01序列稠密一点,外面的稀疏一点) 现在的磁盘其实也是可以做到让外面扇区较大的多存点数据,但是这样的话相关的算法就得更改!! 1.4 回归硬件 所以我们究竟是如何和硬件交互的呢??? 不仅CPU有寄存器,其他外设也有寄存器,包括磁盘!! CPU向磁盘发送IO方向的请求时——> 1、控制寄存器(告诉磁盘是打算读还是打算写) 2、数据寄存器(告诉磁盘要写入哪些数据) 3、地址寄存器(告诉磁盘要写的LBA地址,磁盘自己通过CHS寻址) 4、结果寄存器(CPU获取IO请求是否成功的状态,比如可能空间不足写入失败) 二、文件系统 通过逻辑抽象,我们可以把对扇区的地址抽象成线性的LBA地址,但是具体磁盘有多大呢?已经用了多少扇区?哪些扇区还没被使用?哪些扇区存的是属性?哪些扇区存的是内容?要往哪个扇区去写入??——>诸如以上问题,注定了我们的操作系统必须想办法把磁盘空间组织起来!! 首先是磁盘分区,我们可以把一个磁盘分成多个分区。 而每个分区,都可以用以下的区域来表示 Boot Block: 是文件系统中的一个特殊块,位于文件系统的起始位置。 它包含了引导加载程序(Boot Loader)所需的信息,用于引导操作系统的启动过程。 Block group:每个分区被分成了一个个的block,该block的大小是由格式化确定的,不可被修改,每个block由可以细分为6个区域。 Linux中,文件的属性和内容是分开存储的!! 2.1 inode inode:存储的是单个文件的全部属性(128字节) ,一般而言,一个文件只有一个inode! Linux系统里标记文件的唯一标识用的是inode,且文件的属性中不包含文件的名称!! 通过ls -li 可以观察到文件的inode编号 其实这个信息除了通过这种方式来读取,还有一个stat命令能够看到更多信息 2.2 Data Block Data Block:存文件内容的区域,以块的形式呈现,常见的是4KB大小,一般而言一个块只有自己的数据! 一个文件只有一个inode,但如果是个大文件可能会有很多个块。所以在inode结构体内部会有一个block数组,存储的是数据块的块号。 问题:那难道是文件内容需要多少个数据块,block数组就需要有多大么??? ——>并不是的,block数组内部除了一部分是直接索引(存储文件块号,可直接找到文件内容),还有一小部分是二级索引(该块号不会存储文件内容而是继续存储文件的块号) 2.3 Bitmap 我怎么知道,哪些块被使用过,哪些块没被使用过呢??? 块位图(Block Bitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用 inode那么多,我怎么知道要给该文件分配哪个呢?? inode位图(inode Bitmap):每个bit表示一个inode是否空闲可用。 问题1: 为什么我们下载一个文件需要很久,但是删除的时候却很快呢?? ——>删除一个文件的时候,并不会把块(文件内容)清空,而仅仅只是把对应比特标志位给修改了,表明该块是空闲可用的。 问题2:如果我们想恢复一个被删除的文件,要怎么办呢?? ——> 如果我们不小心误删的一个文件,如果这个文件很重要,最好的办法就是什么都不做然后找专业的人去恢复(一般来说恢复其实就是得找到该文件的inode编号,比如通过Linux的日志信息找到被删文件的inode,但是具体怎么恢复得依靠一些专业的东西!),因为虽然内容还在,但是他的位图信息表明是可以被使用的,所以你如果乱操作可能会导致文件内容被覆盖。 2.4 GDT和超级块 GDT(Group Descriptor Table):块组描述符,描述块组属性信息(该block组的分配信息和使用情况)。 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量, 未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个 文件系统结构就被破坏了 (整个分区的相关信息) 因为超级快是在组里面存在的,但是记录了整个分区的信息,所以并不需要每个组都有, 其实理论上来说有一个就够了(太多会影响速度),但是由于他非常重要,一旦损坏就会造成文件系统崩溃,所以一般来说一个分区里面会有个别组有超级快,这样即使一个超级块崩了,也可以通过其他超级块区对文件系统做修正!! 问题:超级块决定了文件系统能否正常运行,块又那么多,操作系统是如何定位超级块的呢?? ——> 魔数,一个随机值,在超级块中的位置是确定的,只要我们操作系统去读该块的规定偏移量位置看看是否存在魔数,就可以确定该块是不是超级块!! 2.5 格式化 每个分区在使用之前,就必须提前将部分文件系统的属性信息提前设置到对应的分区中,方便我们后续使用这个分区或者分组!! 对磁盘做格式化可以帮助我们让磁盘恢复到完全没有被使用过的状态。 三、对目录的理解 3.1 新建和删除文件,系统做了什么? 1、新建文件:系统会在某个路径下去创建,路径帮助我们确定在哪一个分区,读取了超级块后确定了一个block,然后通过查该block的GDT知道该分区的inode还有很多没有被使用,于是到inodebitmap位图结构里找到了没有用过的inode,然后分配给该文件,分配好之后把自己的属性往里面填,这样就算新建成功了!! 如果还打算写入的话,先确认一下要写入内容的大小,确定一下需要几个块,然后到blockbitmap里面找到没被使用的块,把块号填到inode结构体里面的block数组里,然后再把文件内容写进这些块里面 2、删除文件:先找到分区,再找到inode,然后把对应inodebitmap和blockbitmap的位置置0。其实文件还在,只不过对应的块可以被覆盖!! 我们会发现,无论是什么操作,最重要的就是如何去找到文件的inode,可以我们如何知道一个文件的inode呢??其实使用者压根没关心过inode,我们只关心文件名!! 3.2 目录文件 目录也是文件,也有自己的inode ,也是有内容+属性构成! 重点:目录文件的文件内容放的是文件的文件名+对应文件的inode映射关系!! 问题1:为什么一个目录下不能有同名文件?? ——>因为文件名和inode是key和value的关系,key值必须唯一。 问题2:为什么没有w无法创建文件?? ——>w意味着无法写,所以我们就无法将文件名和inode的映射关系写进去,因此无法创建文件。 问题3:为什么没r无法查看文件?? ——>r意味着无法读,所以我们也看不到文件名和inode的映射关系,找不到inode就找不到文件。 问题4:为什么没有x无法进入目录?? ——>x意味着没有操作权限,要进入一个目录必须cd目录名,并且将当前目录名更新到环境变量PWD中,所以无法进行该操作的话我们就无法进入。 问题5:通过读取目录文件内容可以找到文件的inode,那么目录的inode如何查找呢?? ——>你当前的目录其实也是别的目录的子目录,所以就得递归往上找,一直找到根目录,然后再从根目录往下找回来,读取每个目录的数据块,直到找到该目录的inode.所以访问文件必须带路径!! 3.3 dentry缓存(扩展) 找目录的inode要递归向上找到根目录,然后再找回来,难度不会很慢么??确实会的,所以Linux提供了dentry缓存,将常用文件的inode信息缓存起来!! dentry缓存,简称dcache,是Linux为了提高目录项对象的处理效率而设计的。它是一个slab cache,保存在全局变量dentry_cache中,用于保存目录项的缓存。dentry结构是一种含有指向父节点和子节点指针的双向结构,多个这样的双向结构构成一个内存里面的树状结构,也就是文件系统的目录结构在内存中的缓存了。 linux 内核 目录项高速缓存 dentry cache 简介-CSDN博客 四、软硬链接 建立方式: 4.1 如何理解硬链接? 硬链接不是一个独立的文件,因为他没有独立的inode!! ——>所谓的建立硬链接,本质上就是在特定目录的数据块中新增文件名和指向的文件inode编号的映射关系(有点像起别名的感觉,可以让多个文件名指向一个inode)。 问题1:为什么dir的引用计数是2?? ——>因为 . 是dir的一个硬链接 问题2:为什么dir的上级目录引用计数是3? ——>因为 dir中的 ..是上级目录的一个硬链接 总结: (1)无论他的引用计数是多少,-2就是他当前子目录的数目(比如21,那么他的子目录就是19) (2)所以删除文件并不是直接把该文件的位图清空,本质上是先把目录里该文件的inode映射关系去掉,然后再到inode里面把引用计数-- 引用计数为0时才是把文件的位图清空。 问题3:为什么Linux不允许对目录建立硬链接??(重点!!) ——>为了防止出现环的题,b比方说我们要找一个目录的inode,会向上索引找到根目录,再向下找回来,如果恰好这个过程中出现了 上级目录的硬链接,那么就会回退回去,造成死循环!! 问题4:可是目录内部不是有. 和 .. 的硬链接吗??(重点!!) ——> (1) . 和 .. 是操作系统创建的,他不让你创建是为了你好,担心你创建之后出现环的问题,其实. 和..按照道理也会有环的问题,但是操作系统提前规定好了 .和..不会被做搜索,这是强制规定的!所以不会有环的问题! (2)其实操作系统干嘛要多此一举搞个. 和 ..呢??不就是为了方便让用户使用相对路径么?? 由于目录文件的inode需要递归向上索引才能找到,所以我们总是需要给想要找的文件加上绝对路径,现在操作系统给我们. 和 .. ,我们就可以用相对路径了! 硬链接应用场景:通常用来做路径定位!!可以通过硬链接进行目录切换!(不常用) 4.2 如何理解软链接? 软连接是一个独立的文件,有独立的inode,也有独立的数据块 ,他的内容里面保存的是指向的文件的路径。(相当于windows的快捷方式) 应用场景:当一个可执行文件在路径深处时,我们要找到他比较麻烦,如果我们在当前路径使用,就可以在当前路径建立一个该文件的软连接。(可执行程序随便放都行,只要有软链接,较常用) 五、文件系统和内存系统的关联 5.1 硬件和内存交互的基本单位 物理内存是以4KB为基本单位的(由操作系统决定的) 物理内存交互的单位叫做页框,而磁盘交互的单位叫做页帧 问题:为什么是4KB而不是要多少给多少呢??难道我访问100字节不比访问4KB快吗?? ——>理论上是这样的,但是100字节可能在4KB的不同位置,需要访问的话还需要对磁盘做更精细的计算(磁盘速度太慢了),甚至是文件系统也需要更精细的方法,操作系统文:你能保证你后几秒不用上下文数据吗??反正4kB和100字节效率差不是很多,我都给你拿过来,说不定你用得上呢!(根据局部性原理:程序倾向于访问近期或者近邻的数据,这是计算机性能优化的重要原则,合理运用可以整体提速!) 总结:(1)硬件:减少IO的次数——减少访问外设的次数 (2)软件:基于局部性原理而产生的预加载——整体提速 5.2 操作系统如何管理内存 虚拟地址是操作系统提供的,我们用户只能看到纯的虚拟地址,具体这个虚拟地址具体映射到物理内存的哪个位置,我们并不关心也并不知道,这是操作系统帮我们决定好的,所以操作系统必然可以看得到物理地址!! 那么操作系统要如何去管理我们的内存呢?? Page的配置不能太大,因为这样的话会占据大量的空间。 flag:检查当前page的状态(比如说当前检测到我们要访问的内容并没有被加载到内存中,所以这个时候就会发生缺页中断) count:引用计数(可以通过引用计数知道当前有多少个进程在共享我这个page,当有进程需要去修改内部的数据的时候,就会发生写时拷贝) 所有申请内存的工作,本质上都是访问Page数组!!(根据一个随机地址判断这个地址属于哪个Page的方法:1KB=2^10字节 所以4KB=2^12字节 恰好在16进制地址中就是后三位数字,所以我们任何一个地址只要 & 0xFFFF F000 ,就可以找到该地址对应的Page) 5.3 文件页缓冲区 在Linux中,我们每一个进程打开的每一个文件都具有自己的inod和文件页缓冲区!! 在我们的file结构体狸猫有一个address_spqce结构体,里面又有一个page_tree,里面又有一个 radix_tree_node节点 page_tree这个结构有点类似于B树。他不断延伸最后会找到我们想要寻找的Page结构体! 内存管理,并不是直接让那个我们去访问page数组,而是通过一些配套的算法。 5.4 字典树的理解 以上是一颗相对简单的字典树(组合式的KV模型),比方说我们想要找cba,我们就可以按照这颗树索引下去,找到我们想要找到的内容(某个对象数据) 为什么操作系统要用字典树这个结构来管理我们的page呢?? ——>因为文件的内容按照4KB是有偏移量的。(虽然我们的块是4KB大小,但是我们不一定是在开头去读取或写入,而操作系统通过字典树这种组合式的KV结构来帮助我们定位具体的位置) 5.5 串联进程管理、文件管理、内存管理 首先我们创建了一个进程,创建了一个PCB结构体,然后还会顺便创建一张文件描述符表,我们调用C接口去打开写入文件的时候,该文件会在该文件描述符表中分配一个位置,然后创建一个file结构体,里面分配了inode,但是文件的内容会先写入在C层的缓冲区, 当满足刷新策略的时候,再通过一些方式将其刷新到我们的page中(内核缓冲区)。然后最后再刷新到磁盘中。所以这个过程数据被拷贝了3次 当我们的进程将数据交给内存后,其实他就不管了,所以我们的操作系统必须关心内存要如何刷新到磁盘上,且可能同一时间有大量的IO请求,因此我们的操作系统也要关心先执行哪个请求。所以这就涉及到IO子系统(操作系统和驱动做交互的系统) 我们的IO请求会将相关的一些需求和配置写到一个request结构体里,然后再由操作系统做管理 ——>因为我们的不同的IO请求可能是向不同的扇区写入,所以我们肯定希望比较接近的扇区在一起被写入,这样减少磁头定位能够提高整体效率,所以我们会用一个队列把request管理起来,然后设计一些IO排序和IO合并算法,来给IO请求整体提速!! 在我们开机的时候,因为物理内存经常需要跟磁盘做交互,所以会提前把一些访问物理内存所需要的区域会被预先加载进去,尤其是文件系统的相关功能。 ——>说明操作系统真的帮助我们做了很多事情!! 5.6 slab分配器和伙伴系统(扩展) 从我们创建进程开始,或者是申请文件,我们就会有各种各样的结构体不断地被创建和释放 所以当我们的内核数据结构想要释放的时候,操作系统并不会直接释放,而是会把一些高频使用的内核数据结构体通过某些机制暂时缓存起来,当你下次需要的时候你就不需要跟内存模块做申请,而是直接把之前缓存里面的内核数据结构拿出来初始化即可! slap分配器:Linux 内核 | 内存管理——slab 分配器 - 知乎 (zhihu.com) 伙伴系统:一篇看懂!伙伴系统之伙伴系统概述--Linux内存管理 - 知乎 (zhihu.com) ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_51142926/article/details/142334013
-
我看版本好低,就跑了一个 do-release-upgrade 没想到成功把系统升级崩了。。。中间卡在该死的snap的firefox上面了,一直不动我就给直接关了,没想到到就再也连不上了。。。。这个傻逼snap真的是服了。。。
-
Linux中Vi编辑器的高级用法详解Vi是Linux系统中一款功能强大的文本编辑器,尤其在没有图形界面的服务器环境中,它几乎是唯一的选择。尽管界面简单,但Vi凭借丰富的命令和高效的编辑能力,赢得了程序员们的青睐。本文将详细介绍Vi编辑器的高级用法,包括常用命令、分屏操作、代码折叠、高效编辑技巧等,并附上详细的代码示例,帮助读者快速掌握Vi的高级技巧。一、Vi简介及基本工作模式Vi(Visual Interface)是Linux中最经典的文本编辑器之一,其设计思想是让程序员的手指始终保持在键盘的核心区域,通过命令对文件进行快速编辑。Vi具有三种基本工作模式:命令模式、末行模式和编辑模式。命令模式:打开文件后首先进入命令模式,这是使用Vi的入口。在命令模式下,可以执行定位、翻页、复制、粘贴、删除等常规编辑操作。末行模式:执行保存、退出等操作需要进入末行模式。末行模式是Vi的出口,通过输入命令完成特定操作。编辑模式:在编辑模式下,可以正常地输入和编辑文字。二、Vi的高级用法1. 移动光标与选择文本Vi之所以高效,关键在于能够快速定位到要编辑的代码行。以下是几种常用的移动光标和选择文本的方法:基本移动:使用方向键(h、j、k、l)进行上、下、左、右移动。行内移动:0 移动到行首,^ 移动到行首的非空白字符,$ 移动到行尾。行数移动:gg 移动到文件开头,G 移动到文件末尾,nG 移动到第n行。屏幕移动:Ctrl-F 向前翻页,Ctrl-B 向后翻页,Ctrl-D 向下半页,Ctrl-U 向上半页。可视模式:在选择文本之前,需要先进入可视模式。Vi提供了三种可视模式:字符可视模式、行可视模式和块可视模式。字符可视模式:按v进入,使用方向键选择要复制的文本。行可视模式:按V(Shift+v)进入,选择要复制的行。块可视模式:按Ctrl-v进入,选择矩形块。# 进入可视模式并选择文本 v # 进入字符可视模式 V # 进入行可视模式 Ctrl-v # 进入块可视模式2. 编辑操作在Vi中,编辑操作主要包括删除、复制、粘贴、替换和缩排等。删除文本:x 删除光标所在字符。dw 删除从光标位置到单词末尾的字符。d0 删除从光标位置到行首的字符。d$ 删除从光标位置到行尾的字符。dd 删除当前行。ndd 从光标位置向下连续删除n行。复制文本:yy 复制当前行。nyy 从光标位置向下连续复制n行。yw 复制从光标位置到单词末尾的字符。粘贴文本:p 将缓冲区中的文本粘贴到光标所在位置。替换文本:r 替换光标所在字符。R 进入替换模式,可以连续替换多个字符,按Esc退出替换模式。# 删除和复制文本示例 dw # 删除当前单词 dd # 删除当前行 yy # 复制当前行 p # 粘贴文本3. 撤销和重复撤销:u 撤销上一次编辑操作。重复:Ctrl-r 重复上一次撤销的操作。4. 查找和替换查找:/ 进入查找模式,输入要查找的文本,按Enter开始查找。n 查找下一个匹配项。N 查找上一个匹配项。替换::s/old/new/g 在当前行中全局替换old为new。:1,$s/old/new/g 在整个文件中全局替换old为new。# 查找和替换示例 /pattern # 查找pattern :s/foo/bar/g # 在当前行中替换foo为bar :1,$s/foo/bar/g # 在整个文件中替换foo为bar5. 设置快捷键和别名快捷键:可以在/.vimrc文件中定义快捷键,提高编辑效率。例如,将":map :w"添加到/.vimrc文件中,这样按下F2键就会保存当前文件。# 在~/.vimrc文件中定义快捷键 :map <F2> :w<CR>别名:可以使用缩写来简化输入。例如,在Vi的命令模式下输入":ab jlu Jilin University",之后输入"jlu "就会被自动替换为"Jilin University"。# 定义别名 :ab jlu Jilin University6. 分屏操作Vi支持水平分屏和垂直分屏,可以同时编辑多个文件。水平分屏::sp 文件名 在当前窗口的下方分割一个新窗口。垂直分屏::vsp 文件名 在当前窗口的右侧分割一个新窗口。切换窗口:Ctrl-w + 方向键 切换窗口。# 分屏操作示例 :sp file2.txt # 水平分屏打开file2.txt :vsp file3.txt # 垂直分屏打开file3.txt Ctrl-w h # 切换到左窗口 Ctrl-w j # 切换到下窗口 Ctrl-w k # 切换到上窗口 Ctrl-w l # 切换到右窗口7. 代码折叠Vi支持代码折叠,可以方便地隐藏和显示代码块,提高代码的可读性。折叠方式:使用:set fdm=***命令设置折叠方式,常见的折叠方式有manual、indent、expr、syntax、diff和marker。折叠命令:zc 折叠当前代码块。zC 折叠当前范围内的所有嵌套代码块。zo 打开当前折叠的代码块。zm 折叠更多代码块。zr 打开更多代码块。zE 删除所有折叠。# 设置折叠方式并折叠代码 :set fdm=marker zc # 折叠当前代码块 zo # 打开当前折叠的代码块8. 高效编辑技巧标记和跳转:使用m命令添加标记,使用''或'`命令跳转到标记位置。快速匹配:使用*或#命令查找当前单词的下一个或上一个匹配项。文本对象:使用vi{}、va{}、vi()等命令选中括号内的内容。多行缩进:在可视模式下,使用>或<命令增加或减少缩进。# 标记和跳转示例 ma # 在当前位置添加标记a 'a # 跳转到标记a的位置 # 快速匹配示例 * # 查找当前单词的下一个匹配项 # # 查找当前单词的上一个匹配项 # 文本对象示例 vi{ # 选中{}内的内容(不包括{}) va{ # 选中{}内的内容(包括{})三、总结Vi作为一款功能强大的文本编辑器,在Linux系统中具有不可替代的地位。通过掌握Vi的高级用法,可以大大提高文本编辑的效率。本文详细介绍了Vi的移动光标、选择文本、编辑操作、撤销重复、查找替换、设置快捷键和别名、分屏操作、代码折叠以及高效编辑技巧等高级用法,并附上了详细的代码示例。希望读者通过本文的学习,能够熟练掌握Vi的高级技巧,并在实际工作中灵活运用。
-
我喜爱一切不彻底的事物。琥珀里的时间,微暗的火,一生都在半途而废,一生都怀抱热望。夹竹桃掉落在青草上,是刚刚醒来的风车;静止多年的水,轻轻晃动成冰。我喜爱你忽然捂住我喋喋不休的口,教我沉默。——张定浩前言 时光如流水,来去匆匆,有的时候你并不知道你是虚拟的环境中还是在真实的环境中,一切只是内核kernel的感受而已。。。 一种不同的体验,几种不同的实现方式,那么。。。你又在娜里。。。虚拟化的分类 虚拟化场景其实和现实一样一样的。。。。 在虚拟化的场景中,虚拟机有的时候知道自己运行在虚拟化环境之中,那么这种就需要修改内核,从而也就是1型虚拟化,例如vmware ESX和ESXI,直接由hypervisor来管理硬件,从而提供给上层的虚拟机使用,这个时候,虚拟机使用的内核调用方式就是hyper call,而不是常规的system call了,这种相对于其他的虚拟化技术,效率高了不少(虚拟机必须修改内核才能发起hypervisor call)。 在二型虚拟化中,也就是硬件上面运行的是一个宿主机,然后在其中运行了一个用户空间的程序,用来提供上层虚拟机的管理,这种可以称之为VMM(virtual machine monitor)或者hypervisor,在虚拟机进行调用相关指令的时候,都是发送请求到宿主机,然后被vmm捕获到,然后再进行运行或者调用外部的硬件指令。这种由于指令都要进行最少一次的转换,从而效率损失不少(虚拟机可以运行各种操作系统)。 在现实中,我们进公司的时候,到底是选择外包呢,还是不选外包呢,在使用外包的时候,其实就是第一种类型的虚拟化了,所有人都明确的知道自己在外包之中,从而直接调用外包公司的接口提供服务,这种效率可能很高,但是硬件环境肯定是差的,从而。。。。我也不知道到底是好不好,哈哈、。。。。 在不选择外包的时候,每个人都生活在一个虚拟的空间之中,以为自己使用的是所有的CPU资源,以为自己使用的是所有的物理内存,以为自己能驱动所有的硬件,其实。。。。到底有多少资源能够使用,这个也是不确定的,,,没准,也只是生活在一种虚拟化的场景之中。。。梦中梦。。。内存的虚拟化 在虚拟化场景中,CPU和内存其实是不可分割的,基本上使用的是哪个物理机的CPU,那么就必然会使用其内存,为啥呢? CPU的硬件中,实现了两种主要的芯片,一个是MMU,内存管理单元,主要是用来管理内存的,其实也就是从线性地址转换到物理地址,在进程中,使用的线性地址,也就是每个进程能看到的内存地址其实是整个的物理地址,其中划分了一部分为内核使用,剩余的都是进程使用的内存空间,而每次在进程使用变量的时候,那么这些变量值都是保存在内存之中,那么就要将这个地址在CPU中去找,然后就经过MMU,找到物理地址,而CPU的速度远远大于内存的速度,那么为了匹配这种速度,从而也就有了另外一种芯片,也就是TLB,主要就是用来缓存线性地址到物理地址转换的物理结果,从而每次在寻找地址的时候,都是先找TLB,如果没有那么就会经过MMU转换,然后找到物理内存。 在CPU的物理结构中,有一级缓存,有二级缓存,有三级缓存,三级缓存一般是各个CPU共享的,从而有可能发生资源占用,从而也就从SMP的架构转换到了NUMA架构,从而也就是非一致性内存访问。主要的目的也就是为了提高性能,那么在使用CPU的时候,不可能去别的物理机上找到内存地址,然后读取数据,从而为了提高速度和性能,也就是CPU和MEM基本上是绑定的,在同一个物理机上使用。 超卖的概念,CPU是虚拟的,内存也是虚拟的,其实VCPU也就是虚拟CPU,其实也就是物理机上的一个进程,其实你虚拟的cpu个数可以超过物理的核心数,但是一般超卖的比是1.5,看性能而定,并且要看服务器的压力,也就是CPU的平均负载。内存也是虚拟的,而且有的类库是每个进程或者每个虚拟机都会使用的,从而也可以超过物理内存,但是。。。好像没有一个比例。 在模拟的场景中,一般的步骤就是虚拟机的的虚拟内存转换成虚拟机的物理内存,然后转换成宿主机的虚拟内存,然后找到真正的物理内存,需要经过两次转换,而且存在多个虚拟机的时候,每个虚拟机的TLB基本上都不能命中,从而导致性能大大降低。 从而在硬件层面,提供了硬件辅助的虚拟化技术,例如tagged TLB,也就是对TLB加了一个字段,表示为是哪个虚拟机的线性地址到物理地址的转换;例如提供了MMU的内存单元管理功能,也就是直接将虚拟地址换到物理机的虚拟地址,从而省略了转换到虚拟机的物理地址的步骤,从而能大大的提高性能。 硬支持的内存虚拟化(AMD Nested Page Tables[NPT]和Intel Extended Page Tables[EPT]) I/O的虚拟化 I/O设备,一般分为驱动器和设备本身,而驱动程序运行在内核中,在虚拟话的场景中,如果进程需要调度外部的IO设备,那么首先进程会调用虚拟机的驱动程序,驱动虚拟机的硬件,然后被物理机的vmm捕获,然后将调用物理机的驱动程序,然后才真正的调用物理设备。也就是也分为两步走。 在IO设备的虚拟化场景中,一种是使用模拟的方式,也就是通过软件的模拟,在linux中,一切皆文件,从而也就是提供了一堆的文件进行调用。 一种是使用半虚拟化的方式,这种也是需要硬件的支持的,也就是虚拟机驱动程序直接调用的是物理机的驱动程序,然后驱动硬件,从而省略了虚拟机的驱动调用虚拟机的设备步骤。 最后一种就是IO透传技术,其主要使用的方式就是直接将一些网卡设备或者硬盘设备直接绑定给虚拟机使用,从而基本上达到物理硬件的性能,不过这些硬件的管理还是需要使用hypervisor来进行管理。 硬件支持的设备和I/O虚拟化(Intel VT-d,AMD IOMMU)总结 虚拟化的出现不过是为了更好的利用物理主机的资源,例如CPU,内存,I/O。
-
我喜爱一切不彻底的事物。琥珀里的时间,微暗的火,一生都在半途而废,一生都怀抱热望。夹竹桃掉落在青草上,是刚刚醒来的风车;静止多年的水,轻轻晃动成冰。我喜爱你忽然捂住我喋喋不休的口,教我沉默。——张定浩前言 时光如流水,来去匆匆,有的时候你并不知道你是虚拟的环境中还是在真实的环境中,一切只是内核kernel的感受而已。。。 一种不同的体验,几种不同的实现方式,那么。。。你又在娜里。。。虚拟化的分类 虚拟化场景其实和现实一样一样的。。。。 在虚拟化的场景中,虚拟机有的时候知道自己运行在虚拟化环境之中,那么这种就需要修改内核,从而也就是1型虚拟化,例如vmware ESX和ESXI,直接由hypervisor来管理硬件,从而提供给上层的虚拟机使用,这个时候,虚拟机使用的内核调用方式就是hyper call,而不是常规的system call了,这种相对于其他的虚拟化技术,效率高了不少(虚拟机必须修改内核才能发起hypervisor call)。 在二型虚拟化中,也就是硬件上面运行的是一个宿主机,然后在其中运行了一个用户空间的程序,用来提供上层虚拟机的管理,这种可以称之为VMM(virtual machine monitor)或者hypervisor,在虚拟机进行调用相关指令的时候,都是发送请求到宿主机,然后被vmm捕获到,然后再进行运行或者调用外部的硬件指令。这种由于指令都要进行最少一次的转换,从而效率损失不少(虚拟机可以运行各种操作系统)。 在现实中,我们进公司的时候,到底是选择外包呢,还是不选外包呢,在使用外包的时候,其实就是第一种类型的虚拟化了,所有人都明确的知道自己在外包之中,从而直接调用外包公司的接口提供服务,这种效率可能很高,但是硬件环境肯定是差的,从而。。。。我也不知道到底是好不好,哈哈、。。。。 在不选择外包的时候,每个人都生活在一个虚拟的空间之中,以为自己使用的是所有的CPU资源,以为自己使用的是所有的物理内存,以为自己能驱动所有的硬件,其实。。。。到底有多少资源能够使用,这个也是不确定的,,,没准,也只是生活在一种虚拟化的场景之中。。。梦中梦。。。内存的虚拟化 在虚拟化场景中,CPU和内存其实是不可分割的,基本上使用的是哪个物理机的CPU,那么就必然会使用其内存,为啥呢? CPU的硬件中,实现了两种主要的芯片,一个是MMU,内存管理单元,主要是用来管理内存的,其实也就是从线性地址转换到物理地址,在进程中,使用的线性地址,也就是每个进程能看到的内存地址其实是整个的物理地址,其中划分了一部分为内核使用,剩余的都是进程使用的内存空间,而每次在进程使用变量的时候,那么这些变量值都是保存在内存之中,那么就要将这个地址在CPU中去找,然后就经过MMU,找到物理地址,而CPU的速度远远大于内存的速度,那么为了匹配这种速度,从而也就有了另外一种芯片,也就是TLB,主要就是用来缓存线性地址到物理地址转换的物理结果,从而每次在寻找地址的时候,都是先找TLB,如果没有那么就会经过MMU转换,然后找到物理内存。 在CPU的物理结构中,有一级缓存,有二级缓存,有三级缓存,三级缓存一般是各个CPU共享的,从而有可能发生资源占用,从而也就从SMP的架构转换到了NUMA架构,从而也就是非一致性内存访问。主要的目的也就是为了提高性能,那么在使用CPU的时候,不可能去别的物理机上找到内存地址,然后读取数据,从而为了提高速度和性能,也就是CPU和MEM基本上是绑定的,在同一个物理机上使用。 超卖的概念,CPU是虚拟的,内存也是虚拟的,其实VCPU也就是虚拟CPU,其实也就是物理机上的一个进程,其实你虚拟的cpu个数可以超过物理的核心数,但是一般超卖的比是1.5,看性能而定,并且要看服务器的压力,也就是CPU的平均负载。内存也是虚拟的,而且有的类库是每个进程或者每个虚拟机都会使用的,从而也可以超过物理内存,但是。。。好像没有一个比例。 在模拟的场景中,一般的步骤就是虚拟机的的虚拟内存转换成虚拟机的物理内存,然后转换成宿主机的虚拟内存,然后找到真正的物理内存,需要经过两次转换,而且存在多个虚拟机的时候,每个虚拟机的TLB基本上都不能命中,从而导致性能大大降低。 从而在硬件层面,提供了硬件辅助的虚拟化技术,例如tagged TLB,也就是对TLB加了一个字段,表示为是哪个虚拟机的线性地址到物理地址的转换;例如提供了MMU的内存单元管理功能,也就是直接将虚拟地址换到物理机的虚拟地址,从而省略了转换到虚拟机的物理地址的步骤,从而能大大的提高性能。 硬支持的内存虚拟化(AMD Nested Page Tables[NPT]和Intel Extended Page Tables[EPT]) I/O的虚拟化 I/O设备,一般分为驱动器和设备本身,而驱动程序运行在内核中,在虚拟话的场景中,如果进程需要调度外部的IO设备,那么首先进程会调用虚拟机的驱动程序,驱动虚拟机的硬件,然后被物理机的vmm捕获,然后将调用物理机的驱动程序,然后才真正的调用物理设备。也就是也分为两步走。 在IO设备的虚拟化场景中,一种是使用模拟的方式,也就是通过软件的模拟,在linux中,一切皆文件,从而也就是提供了一堆的文件进行调用。 一种是使用半虚拟化的方式,这种也是需要硬件的支持的,也就是虚拟机驱动程序直接调用的是物理机的驱动程序,然后驱动硬件,从而省略了虚拟机的驱动调用虚拟机的设备步骤。 最后一种就是IO透传技术,其主要使用的方式就是直接将一些网卡设备或者硬盘设备直接绑定给虚拟机使用,从而基本上达到物理硬件的性能,不过这些硬件的管理还是需要使用hypervisor来进行管理。 硬件支持的设备和I/O虚拟化(Intel VT-d,AMD IOMMU)总结 虚拟化的出现不过是为了更好的利用物理主机的资源,例如CPU,内存,I/O。
-
客户端: 功能: 连接:获取ip地址和端口号,并连接 取消:关闭窗口 工程文件加上 QT=+=core gui network 相关类与函数 QTcpSocket类:(需要被包含) connectToHost() 连接。参数:QHostAdress地址对象(可用QString初始化),端口号(16位短整型)(QString自带进制转换,如ip.toshort) 连接成功后会发出一个信号:&QTcpSocket::connected 连接异常也会发出一个信号:&QTcpSocket::disconnected peerAddress();获取客户端地址 peerAddress的子函数toString():将其转化位字符串 peerPort():获取客户端端口号(并非服务器端口) write():参数:QByteArray ,可用append拼接,发送后会出现&QTcpSocket::readyread信号 QHostAddress类:(需要被包含) 其他函数: (QTcpSocket * )sender();获取信号的发送者并强制转为QTcpSocket指针 服务器: 相关类与函数: QTcpServer类: listen():监听网卡,参数QHostAdress::AnyIPv4,端口号 如果有信号,会发出QTcpServer::newConnection信号 nextPendingConnection() 建立TCP连接,返回QTcpSocket类型 实现服务器与客户端连接 在widget构造函数中监听网卡ip,等待请求,收到信号后与客户端建立连接 注意,必须使用127开头的地址 127.0.0.1 地址称为本地回环地址,是一种特殊的网络地址,用于让单独的计算机进行自我回路测试和通信。 这个地址在 IP 协议中被定义为环回地址。 在网络设备中,网络接口上的 127.0.0.1 地址本质上是本机对自己的网络地址。 通信 打开其他窗口:新建qt->设计师...,在客户端创建新的窗口类chat this->hide()隐藏窗口 创建新窗口对象时要用堆空间创建,也即是指针形式,这样申请的内存不会被销毁, widget在连接成功后隐藏当前窗口,并打开新窗口chat,与打开widget窗口类似 chat *k=new chat(SOCKET); k->show(); 要将socket信息传到新窗口中,所以类chat中要有参数,并在chat构造函数中初始化 QTcpSocket *s 在新窗口中,使用socket中的write发送信息,参数是QByteArray类型 在服务器中连接后再connect一个新的信号与曹: &QTcpSocket::readyRead &Widget::data_resslot 在槽函数中要注意变量的范围,如果QTcpSocket指针是内部变量,则需要使用 (QTcpSocket*)sender(); 将其转化为QTcpSocket类型指针,再用readAll读取所有发送的数据。 服务器代码: chat.h #ifndef CHAT_H #define CHAT_H #include <QWidget> #include <QTcpSocket> namespace Ui { class chat; } class chat : public QWidget { Q_OBJECT public: explicit chat(QTcpSocket *s,QWidget *parent = nullptr); ~chat(); private slots: void on_clearButton_clicked(); void on_sendButton_2_clicked(); private: Ui::chat *ui; QTcpSocket *SOCKET; }; #endif // CHAT_H widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpSocket> #include <QHostAddress> #include <QDebug> #include <QMessageBox> #include <chat.h> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_cancelButton_2_clicked(); void on_connectButton_clicked(); void on_pushButton_clicked(); private: Ui::Widget *ui; QTcpSocket *SOCKET; }; #endif // WIDGET_H chat.cpp #include "chat.h" #include "ui_chat.h" chat::chat(QTcpSocket *s,QWidget *parent) : QWidget(parent), ui(new Ui::chat) { ui->setupUi(this); SOCKET=s; } chat::~chat() { delete ui; } void chat::on_clearButton_clicked() { ui->lineEdit->clear(); } void chat::on_sendButton_2_clicked() { QString text1=ui->lineEdit->text(); QByteArray ba; ba.append(text1); //write只能发送QByteArray类型数据 SOCKET->write(ba); } widget.cpp #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); SOCKET=new QTcpSocket; //成功/异常警告 connect(SOCKET,&QTcpSocket::connected,[this]() { QMessageBox::information(this,"提示","连接成功"); this->hide(); chat *k=new chat(SOCKET); k->show(); }); connect(SOCKET,&QTcpSocket::disconnected,[this]() { QMessageBox::warning(this,"警告","连接异常"); }); } Widget::~Widget() { delete ui; } void Widget::on_cancelButton_2_clicked() { this->close(); } void Widget::on_connectButton_clicked() { //获取ip与端口号 QString com1=ui->COMlineEdit_2->text(); QString ip1=ui->IPlineEdit->text(); if(com1.isEmpty()||ip1.isEmpty()) { QMessageBox::warning(this,"警告","请输入ip地址和端口号"); } //开始连接 SOCKET->abort(); SOCKET->connectToHost(QHostAddress(ip1),com1.toShort()); } void Widget::on_pushButton_clicked() { qDebug()<<"disconnect"; SOCKET->disconnectFromHost(); } 服务器代码: widget.h #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QDebug> #include <QTcpServer> #include <QTcpSocket> #include <QHostAddress> #include <QMessageBox> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE #define TCPCOM 8000 class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void serverslot(); void on_check_clicked(); void data_resslot(); private: Ui::Widget *ui; QTcpServer *SERVER; }; #endif // WIDGET_H widget.cpp #include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) { ui->setupUi(this); SERVER=new QTcpServer; SERVER->listen(QHostAddress::Any,TCPCOM); qDebug()<<TCPCOM; connect(SERVER,&QTcpServer::newConnection,this,&Widget::serverslot); } Widget::~Widget() { delete ui; } void Widget::serverslot() { QTcpSocket *SOCKET1; SOCKET1=SERVER->nextPendingConnection(); QHostAddress ip1=SOCKET1->peerAddress(); quint16 com1=SOCKET1->peerPort(); ui->comlineEdit->setText(QString::number(com1)); ui->iplineEdit_2->setText(ip1.toString()); QMessageBox::information(this,"提示","连接成功"); connect(SOCKET1,&QTcpSocket::readyRead,this,&Widget::data_resslot); } void Widget::on_check_clicked() { if(SERVER->isListening()) { qDebug()<<"IS LISTENING"; } } void Widget::data_resslot() { QTcpSocket *Socket=(QTcpSocket*)sender(); ui->datalineEdit->setText(Socket->readAll()); } 结果: 多线程 在服务器中启用多线程,即可接收多个客户端的连接。 相关类与函数 在文件中添加c++类,在继承中继承QThread,添加后多了.h和.cpp文件 QThread类,需要包含 start()打开线程 虚函数run():在run里连接信号与槽,线程处理函数 自定义信号 在类中定义,和private同级,在信号传递时可以附加参数 signals: void sendtowidget(QByteArray ba); 在需要发出信号的地方加上即可,最后在widget中连接信号与槽 QByteArray ba=socket->readall(); emit sendtowidget(ba); ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/qq_59612601/article/details/139992170
-
在数字化时代,互联网已经成为我们日常生活和工作中不可或缺的一部分。无论是家庭网络还是企业办公环境,每台接入网络的设备都需要一个独特的标识来区分彼此,这个标识就是IP地址。IP地址全称为“互联网协议地址”,是设备在网络中的唯一身份标识。那么,什么是内网IP地址?如何查询电脑内网IP地址?下面跟着虎观代理小二一起来了解一下吧。 原图请到原文查看 一、内网IP地址概述 什么是内网IP地址?内网IP地址是指在局域网内部使用的IP地址,通常由路由器或局域网服务器分配。与全球唯一的外网IP地址不同,内网IP地址只在局域网内部有效,不同局域网中的设备可以拥有相同的内网IP地址而不会产生冲突。内网IP地址的范围通常在192.168.0.0至192.168.255.255之间,这是由IPv4地址的私有地址段决定的。 二、内网IP地址的作用 内网IP地址在局域网内部扮演着重要的角色。它使得局域网内的设备能够相互识别并进行通信,无论是文件共享、打印机访问还是局域网游戏等应用,都离不开内网IP地址的支持。同时,内网IP地址还提供了网络安全的基础,通过合理的IP地址规划和管理,可以实现对网络流量的监控和控制,提高网络的安全性。 三、如何查询电脑内网IP地址 查询电脑内网IP地址有多种方法,以下是几种常用的方法: 1、通过命令提示符(CMD)查询 按下【Win+R】键打开运行对话框,输入【cmd】并按回车,打开命令提示符窗口。 在命令提示符窗口中,输入【ipconfig】并按回车。 在显示的信息中,找到IPv4 地址一项,其后的数字即为电脑的内网IP地址。 2、通过网络连接设置查询 点击任务栏右下角的网络图标,在弹出的菜单中选择【网络和Internet设置】。 在设置窗口中,点击【状态】,向下滚动并点击【查看硬件和连接属性】。 在新窗口中,找到正在使用的网络适配器,并查看“IPv4 地址”。 3、使用PowerShell命令查询 在搜索栏中输入【PowerShell】并打开它。 输入【Get-NetIPAddress】命令并按回车。 在返回的信息中,查找“IPv4Address”属性,这将显示您的内网IP地址。 4、通过控制面板查询 按下【Win+R】键打开运行对话框,输入【control】并按回车,打开控制面板窗口。 在控制面板中,选择【网络和Internet】,然后点击【网络和共享中心】。 点击当前连接的网络名称,选择【详细信息】。 在网络连接详细信息窗口中,找到“IPv4地址”字段。 结尾: 通过本文的介绍,相信读者已经对内网IP地址有了更深入的了解。内网IP地址作为局域网内部设备的唯一标识,对于网络的正常运行和管理至关重要。掌握查询内网IP地址的方法,不仅可以帮助我们更好地理解和使用网络环境,还能在出现问题时迅速定位并解决。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/hgdlip/article/details/140762911
-
在 Ubuntu 中,如果遇到可以联网但是无法打开许多网页的问题,这可能是 DNS 设置不正确或者网络配置有误引起的。重置网络配置到默认设置可以帮助解决这类问题。以下是几种方法来重置 Ubuntu 的网络配置: ### 1. 重启网络服务 有时候简单地重启网络服务就能解决问题: ```bash sudo systemctl restart NetworkManager ``` ### 2. 重新获取 DHCP 分配的地址 如果您的网络配置是自动通过 DHCP 获取的,可以尝试释放当前 IP 并重新获取: ```bash sudo dhclient -r sudo dhclient ``` ### 3. 重置网络配置文件 如果上述方法都没有解决问题,可以尝试手动重置网络配置。对于使用 Netplan 的 Ubuntu 系统(通常是 Ubuntu 18.04 及以后的版本),可以按照以下步骤操作: 1. **查找 Netplan 配置文件**: ```bash ls /etc/netplan/ ``` 这将列出所有的网络配置文件。 2. **编辑或恢复默认配置**: 使用文本编辑器编辑文件,例如: ```bash sudo nano /etc/netplan/01-netcfg.yaml ``` 将内容修改为以下形式(根据实际接口名称调整 `eth0` 或其他网络接口名): ```yaml network: version: 2 ethernets: eth0: dhcp4: true ``` 保存并退出编辑器。 3. **应用配置**: ```bash sudo netplan apply ``` ### 4. 检查 DNS 设置 问题可能与 DNS 解析有关,确保 DNS 设置正确: - 查看当前 DNS 配置: ```bash systemd-resolve --status ``` - 可以尝试设置一个公共 DNS(如 Google DNS 8.8.8.8 和 8.8.4.4)来看是否解决问题。编辑 Netplan 配置文件,在适当的部分添加 DNS 服务器: ```yaml nameservers: addresses: - 8.8.8.8 - 8.8.4.4 ``` 保存并应用配置: ```bash sudo netplan apply ``` ### 5. 检查防火墙设置 有时防火墙设置可能导致无法访问特定网站。检查防火墙规则,并尝试暂时禁用防火墙: ```bash sudo ufw disable ``` 如果禁用后可以访问网页,那么问题可能在于防火墙配置。 尝试这些方法后,如果问题依然存在,可能需要更详细地检查网络环境或咨询您的网络服务提供商。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/u013559309/article/details/139553532
-
前言 今天也是超爱你的一天哦! mkdir指令 在当前目录下创建一个名为 “dirname”的目录 -p, --parents 可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后,系统将自动建立 好那些尚不存在的目录,即一次可以建立多个目录 rmdir指令 && rm 指令 rmdir是一个与mkdir相对应的命令。mkdir是建立目录,而rmdir是删除命令。 语法:rmdir [-p][dirName] 适用对象:具有当前目录操作权限的所有使用者 功能:删除空目录 常用选项: -p 当子目录被删除后如果父目录也变成空目录的话,就连带父目录一起删除。 rm命令可以同时删除文件或目录 语法:rm [-f-i-r-v][dirName/dir] 适用对象:所有使用者 功能:删除文件或目录 常用选项: -f 即使文件属性为只读(即写保护),亦直接删除 -i 删除前逐一询问确认 -r 删除目录及其下所有文件 man指令 Linux的命令有很多参数,我们不可能全记住,我们可以通过查看联机手册获取帮助。访问Linux手册页的命令是 man 语法: man [选项] 命令 -k 根据关键字搜索联机帮助 num 只在第num章节找 -a 将所有章节的都显示出来,比如 man printf 它缺省从第一章开始搜索,知道就停止,用a选项,当按 下q退出,他会继续往后面搜索,直到所有章节都搜索完毕。 解释一下,面手册分为8章 第 1 部分(man 1)包含单行命令的帮助,如 man ls; 第 2 部分(man 2)是系统调用(system calls),如 man open; 第 3 部分(man 3)涵盖库函数,例如 man malloc; 第 4 部分(man 4)是设备驱动程序和网络协议, 而第 5 部分(man 5)涉及配置文件和其他系统管理文件。 此外,还有第 6、7 和 8 部分,分别用于程序游戏规则(games)、系统文档结构和进程与线程控制等。 cp指令 语法:cp [选项] 源文件或目录 目标文件或目录 功能: 复制文件或目录 说明: cp指令用于复制文件或目录,如同时指定两个以上的文件或目录,且最后的目的地是一个已经存在的目录, 则它会把前面指定的所有文件或目录复制到此目录中。若同时指定多个文件或目录,而最后的目的地并非一个已存 在的目录,则会出现错误信息 -f 或 --force 强行复制文件或目录, 不论目的文件或目录是否已经存在 -i 或 --interactive 覆盖文件之前先询问用户 -r递归处理,将指定目录下的文件与子目录一并处理。若源文件或目录的形态,不属于目录或符号链 接,则一律视为普通文件处理 -R 或 --recursive递归处理,将指定目录下的文件及子目录一并处理 mv指令 mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命 令,经常用来备份文件或者目录。 语法: mv [选项] 源文件或目录 目标文件或目录 功能: 1. 视mv命令中第二个参数类型的不同(是目标文件还是目标目录),mv命令将文件重命名或将其移至一个新的 目录中。 2. 当第二个参数类型是文件时,mv命令完成文件重命名,此时,源文件只能有一个(也可以是源目录名),它 将所给的源文件或目录重命名为给定的目标文件名。 3. 当第二个参数是已存在的目录名称时,源文件或目录参数可以有多个,mv命令将各参数指定的源文件均移至 目标目录中。 -f :force 强制的意思,如果目标文件已经存在,不会询问而直接覆盖 -i :若目标文件 (destination) 已经存在时,就会询问是否覆盖! less指令 less 工具也是对文件或其它输出进行分页显示的工具,应该说是linux正统查看文件内容的工具,功能极 其强大。 less 的用法比起 more 更加的有弹性。 在 more 的时候,我们并没有办法向前面翻, 只能往后面看 但若使用了 less 时,就可以使用 [pageup][pagedown] 等按键的功能来往前往后翻看文件,更容易用 来查看一个文件的内容! 除此之外,在 less 里头可以拥有更多的搜索功能,不止可以向下搜,也可以向上搜 语法: less [参数] 文件 功能: less与more类似,但使用less可以随意浏览文件,而more仅能向前移动,却不能向后移动,而且less在查看之前 不会加载整个文件。 -i 忽略搜索时的大小写 -N 显示每行的行号 /字符串:向下搜索“字符串”的功能 ?字符串:向上搜索“字符串”的功能 n:重复前一个搜索(与 / 或 ? 有关) N:反向重复前一个搜索(与 / 或 ? 有关) q:quit find指令 Linux下find命令在目录结构中搜索文件,并执行指定的操作。 Linux下find命令提供了相当多的查找条件,功能很强大。 由于find具有强大的功能,所以它的选项也很 多,其中大部分选项都值得我们花时间来了解一下。 即使系统中含有网络文件系统( NFS),find命令在该文件系统中同样有效,只你具有相应的权限。 在运行一个非常消耗资源的find命令时,很多人都倾向于把它放在后台执行,因为遍历一个大的文件系 统可能会花费很长的时间(这里是指30G字节以上的文件系统)。 语法: find pathname -options 功能: 用于在文件树种查找文件,并作出相应的处理(可能访问磁盘) 常用选项:-name 按照文件名查找文件。 tar指令:打包/解包,不打开它,直接看内容 tar [-cxtzjvf] 文件与目录 .... 参数: -c :建立一个压缩文件的参数指令(create 的意思); -x :解开一个压缩文件的参数指令! -t :查看 tarfile 里面的文件! -z :是否同时具有 gzip 的属性?亦即是否需要用 gzip 压缩? -j :是否同时具有 bzip2 的属性?亦即是否需要用 bzip2 压缩? -v :压缩的过程中显示文件!这个常用,但不建议用在背景执行过程! -f :使用档名,请留意,在 f 之后要立即接档名喔!不要再加参数! -C : 解压到指定目录 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/ormstq/article/details/140313132
-
一、什么是 CPU 使用率 Linux 作为一个多任务操作系统,将每个 CPU 的时间划分为很短的时间片,再通过调度器轮流分配给各个任务使用,因此造成多任务同时运行的错觉。 为了维护 CPU 时间, Linux 通过事先定义的节拍率(内核中表示为 HZ),触发时间中断,并使用全局变量 Jiffies 记录了开机以来的节拍数。每发生一次时间中断,Jiffies 的值就加 1。节拍率 HZ 是内核的可配选项,可以设置为 100、250、1000 等。不同的系统可能设置不同数值。 Linux 通过 /proc 虚拟文件系统,向用户空间提供了系统内部状态的信息,而 /proc/stat 提供的就是系统的 CPU 和任务统计信息。比方说,如果你只关注 CPU 的话,可以执行下面的命令: 第一列表示的是 CPU 编号,如 cpu0、cpu1 ,而第一行没有编号的 cpu ,表示的是所有 CPU 的累加结果。其他列则表示不同场景下 CPU 的累加节拍数,它的单位是 USER_HZ,也就是 10 ms(1/100 秒),所以这其实就是不同场景下的 CPU 时间。下面,依次为各列的含义: user(通常缩写为 us),代表用户态 CPU 时间。注意,它不包括下面的 nice时间,但包括了 guest 时间。 nice(通常缩写为 ni),代表低优先级用户态 CPU 时间,也就是进程的 nice 值被调整为 1-19 之间时的 CPU 时间。这里注意,nice 可取值范围是 -20 到 19,数值越大,优先级反而越低。 system(通常缩写为 sys),代表内核态 CPU 时间。 idle(通常缩写为 id),代表空闲时间。注意,它不包括等待 I/O 的时间(iowait)。 iowait(通常缩写为 wa),代表等待 I/O 的 CPU 时间。 irq(通常缩写为 hi),代表处理硬中断的 CPU 时间。 softirq(通常缩写为 si),代表处理软中断的 CPU 时间。 steal(通常缩写为 st),代表当系统运行在虚拟机中的时候,被其他虚拟机占用的 CPU 时间。 guest(通常缩写为 guest),代表通过虚拟化运行其他操作系统的时间,也就是运行虚拟机的 CPU 时间。 guest_nice(通常缩写为 gnice),代表以低优先级运行虚拟机的时间。 而我们通常所说的 CPU 使用率,就是除了空闲时间外的其他时间占总 CPU 时间的百分比,用公式来表示就是: C P U 使用率 = 1 − 空闲时间 总 C P U 时间 CPU 使用率 = 1 - \frac{空闲时间}{总 CPU时间} CPU使用率=1− 总CPU时间 空闲时间 但直接用 /proc/stat 的数据这是开机以来的节拍数累加值,所以直接算出来的,是开机以来的平均 CPU 使用率,一般没有什么参考价值。 事实上,为了计算 CPU 使用率,性能工具一般都会取间隔一段时间(比如 1 秒)的两次值,作差后,再计算出这段时间内的平均 CPU 使用率,即: 平均 C P U 使用率 = 1 − 空闲时 间 n e w − 空闲时 间 o l d 总 C P U 时 间 n e w − 总 C P U 时 间 o l d 平均 CPU 使用率 = 1 - \frac{空闲时间_{new} - 空闲时间_{old}}{总 CPU时间_{new} - 总 CPU时间_{old}} 平均CPU使用率=1− 总CPU时间 new −总CPU时间 old 空闲时间 new −空闲时间 old 跟系统的指标类似,Linux 也给每个进程提供了运行情况的统计信息,也就是 /proc/[pid]/stat。不过,这个文件包含的数据就比较丰富了,总共有 52 列的数据,这里不再赘述。 二、查看 CPU 利用率 1、使用 top 查看 top 是一个命令行工具安装在任何 Linux 系统上,它主要提供由 Linux 内核管理的所有当前运行任务的动态实时统计汇总。它监视 Linux 系统上进程、CPU 和内存的完整利用率。 $ top 1 结果如下: us: 花费在用户空间上的 CPU 时间百分比 (running user-spawned processes)。 sy: 花费在内核空间的 CPU 时间百分比(running system processes)。 ni: 运行用户定义优先级的进程所花费的 CPU 时间百分比(aspecified nice value)。 id: CPU 空闲时间的百分比。 wa: 用于等待硬件 I/O 的 CPU 时间百分比。例如:等待硬盘读完数据。 hi: 用于处理硬件中断的 CPU 时间百分比。例如:网卡(或任何硬件)中断 CPU 以通知它有新数据到达。 si: 用于处理软件中断的 CPU 时间百分比。例如:高优先级业务导致 CPU 中断。 st: 从虚拟机窃取的 CPU 时间百分比。例如:为了处理物理机的工作负载,需要从虚拟机“窃取”资源的 CPU 可以非常清楚地看到 CPU 使用率。不过,要注意的是,CPU 默认显示的是所有 CPU 的平均值。这时候,按下数字 1 就可以看到每个 CPU 的使用率了: 继续往下看,空白行之后是进程的实时信息,每个进程都有一个 %CPU 列,表示进程的 CPU 使用率。它是用户态和内核态 CPU 使用率的总和,包括进程用户空间使用的 CPU、通过系统调用执行的内核空间 CPU 、以及在就绪队列等待运行的 CPU。在虚拟化环境中,它还包括了运行虚拟机占用的CPU。 可以发现,top 并没有细分进程的用户态 CPU 和内核态 CPU。 2、用 pidstat 查看 pidstat 是一个专门分析每个进程 CPU 使用情况的工具。比如,下面的 pidstat 命令,就间隔 1 秒展示了进程的 5 组 CPU 使用率,包含: 用户态CPU使用率(%usr); 内核态CPU 使用率(%system) 运行虚拟机CPU使用率(%guest) 等待 CPU使用率(%wait); 总的CPU使用率(%CPU) 最后的 Average 部分,还计算了5组数据的平均值。 3、用 ps 查看 ps 命令可用于确定哪个进程占用了 CPU。 $ ps -eo pid,%cpu,cmd --sort=-%cpu 1 如果要仅查看正在运行的进程,可以使用以下命令: $ ps -eo pid,%cpu,cmd --sort=-%cpu | grep -v PID 1 这个命令将过滤掉标题行。 如果要仅显示前几个进程,可以将输出通过head命令进行截取。例如,要显示前 5 个进程,可以使用以下命令: $ ps -eo pid,%cpu,cmd --sort=-%cpu | grep -v PID | head -n 5 1 4、用 htop 查看 htop 是一个交互式的进程查看器和系统监控工具,它提供了比传统的 top 命令更多的功能和更友好的界面。它提供了 CPU 和系统资源利用率的详细摘要。可以垂直滚动或水平滚动以显示更多详细信息。它还在命令列下提供进程路径。 $ htop 1 在 CPU 状态区域主要显示 CPU 使用情况,htop 还为将不同颜色来区分是使用情况: 蓝色的表示 low-prority(低优先级)使用 绿色的表示 normal(标准)使用情况 红色的表示 kernel(内核)使用情况 青色的表示 virtuality(虚拟性)使用情况 5、用 nmon 查看 nmon 是 Linux 系统的一个性能监控工具。它用于实时监测系统的 CPU、内存、磁盘、网络等资源的使用情况,帮助管理员分析系统性能并做出优化调整。 nmon 工具以文字界面的形式展示监控结果,提供了丰富的信息和统计数据,可以实时查看系统的运行状况。它的输出格式清晰简洁,容易理解和分析。 使用 nmon 命令: $ nmon 1 然后按 t 查看利用更多资源的进程: 按下 c 键可以来查看 CPU 的使用率: 6、用 atop 查看 atop 是在 Linux 系统的一个高级性能监控工具。与 nmon 类似,atop 也用于实时监测系统的各种资源的使用情况,但它提供了更为详细和全面的性能数据,让管理员更深入地了解系统运行情况。 $ atop 1 也可以使用以下命令把 CPU 使用率记录到文件中,下次直接读取文件查看 CPU 利用率。写入文件使用“-w 文件名”,读出文件使用“-r 文件名”: $ root@projectsauron:~# atop -w test ^C $ root@projectsauron:~# atop -r test 1 2 3 7、用 glances 查看 glances 是一种跨平台的实时系统监控工具,该工具是用python编写的,并使用库程序详细提供了对 CPU、内存、磁盘、网络和进程等系统资源的监测信息。glances 以直观和交互的方式呈现系统监控数据,并提供了丰富的选项和功能,方便用户进行系统性能的实时监控和分析。 8、用 vmstat 查看 vmstat(virtual memory statistics)是一个在 Linux 系统上用于监视虚拟内存、进程、CPU 和 IO 性能的命令行工具。 $ vmstat 1 各个字段你含义如下: 进程 procs r:在运行队列中等待的进程数 。 b:在等待io的进程数 。 内存 memoy: swpd:现时可用的交换内存(单位KB)。 free:空闲的内存(单位KB)。 buff: 缓冲去中的内存数(单位:KB)。 cache:被用来做为高速缓存的内存数(单位:KB)。 swap 交换页面 si: 从磁盘交换到内存的交换页数量,单位:KB/秒。 so: 从内存交换到磁盘的交换页数量,单位:KB/秒。 io 块设备: bi: 发送到块设备的块数,单位:块/秒。 bo: 从块设备接收到的块数,单位:块/秒。 system 系统: in: 每秒的中断数,包括时钟中断。 cs: 每秒的环境(上下文)转换次数。 cpu 中央处理器: cs:用户进程使用的时间 。以百分比表示。 sy:系统进程使用的时间。 以百分比表示。 id:中央处理器的空闲时间 。以百分比表示。 如果想使用 vmstat 命令以 2 秒的间隔监视系统资源,间隔 5 次。在这种情况下,可以使用以下命令: $ vmstat 2 5 1 9、用 sar 查看 sar(System Activity Reporter)是一个在 Linux 系统上用于收集、报告和存档系统活动数据的命令行工具。 使用 sar 命令,可以按特定时间间隔监视 CPU 使用率: $ sar 3 1 也可以通过如下来显示每隔 3 秒 10 条后的运行数据。 $ sar 3 10 1 10、dstat dstat 是一个在 Linux 系统上使用的用于监控系统资源使用情况的命令。它可以提供关于 CPU、内存、磁盘、网络等方面的实时数据,并以可读性高的格式输出。该工具结合了 vmstat,iostat,ifstat,netstat 以及更多的信息。并且支持输出 CSV 格式报表,并能导入到 Gnumeric 和 Excel 以生成图形 $ dstat 1 每秒 CPU 使用率情况获取: $ dstat -c 1 最占 CPU 的进程获取: $ dstat --top-cpu 1 11、iostat 这个命令主要用来查看 IO 使用情况,也可以来查看 CPU,不如上述命令常用。 三、总结 Linux 系统上有各种可用的工具和命令来监视 CPU 使用率和系统资源利用率,那么多命令工具可根据实际情况,直观可视化的 nmon 和 galances 比较方便查看结果。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/Teminator_/article/details/140999346
-
1. 进程创建 1.1. 操作系统的工作内容 当一个进程调用fork函数创建一个新的子进程时,操作系统会执行以下主要步骤:分配资源 -> 复制信息 ->添加到系统进程列表 -> 返回值 -> 调度器调度。 分配资源:OS给子进程分配新的内存块(虚拟地址空间),和其他必要的内核数据结构(PCB、页表等)。 复制信息:将父进程的数据结构内容的一部分拷贝给子进程。 添加到系统进程列表:OS会将子进程的信息添加到系统的进程管理数据结构中(进程链表等),这样系统就能够管理和跟踪该子进程的状态和行为,调度器就能够感知新的进程并对其进行调度。 返回值:为了区分父子进程,并允许它们执行不同的代码路径(if…else分流)。 调度器调度:一旦fork操作完成,OS调度器就会重新安排CPU时间片,调度器可能会根据调度算法选择父进程、子进程、或系统中其他进程来执行,这意味着父进程,子进程可能会并发执行,但它们的执行顺序是不确定的。 1.2. fork常规用法 父进程希望复制自己,根据fork返回值使用if…else进行分流,从而使父子进程执行相同程序中不同的代码段。例如:父进程等待客户端请求,生成子进程来处理请求。 子进程要执行与父进程完全不同的程序。在这种情况下,子进程从fork返回后,通过调用exec()系列函数,在当前进程中加载并运行一个新的程序(进程程序替换)。例如:需要执行特定任务的子进程,这些任务与父进程的主要职责不同。 1.3. fork调用失败的原因 系统中有太多进程。如:一个学校能容纳的学生总数是有限的。 实际用户的进程数超过了限制。如:一个班级能容纳的学生总数是有限的。 2. 进程终止 2.1. main函数的返回值 2.1.1. 退出码 退出码:main函数的返回值,用来表示进程退出(终止)时,其执行结果是否正确。 main函数返回0,表示代码执行成功,结果正确或者符合预期的。 main函数返回非0,表示代码执行成功,结果是不正确的或程序遇到错误/异常情况。 代码执行成功,程序能够执行到main函数的末尾并返回,而不是说程序中的每一行都按预期执行了,因为有些错误不能被捕获或者导致程序提前退出了。 非0返回值,通常用于表示不同类型错误/异常的原因,退出码的字符串含义取决于程序的设计者。 Shell(bash)会自动记录最近一次子进程执行完毕时的退出码,这个退出码可以通过echo $?获取。 echo $? 功能:获取上一个命令的退出码。 #include<stdio.h> int main() { printf("hello world\n"); return 0; //退出码 } 2.1.2. 退出码转化为错误描述的方式 使用语言或者系统自带的方法进行转化,例如:在linux中,使用strerror()函数。 char* strerror(int errnum); #include<stdio.h> #include<string.h> int main() { for(int i = 0; i < 100; i++) printf("%d:%s\n", i, strerror(i)); return 0; } 2. 使用枚举类型进行自定义。 #include<stdio.h> #include<string.h> enum{ success=0, malloc_err, open_err }; const char* errorDesc(int code) { switch(code) { case success: return "running sucess!"; case malloc_err: return "malloc failure!"; case open_err: return "file open failure!"; default: return "unkown error!"; } } int main() { int exit_code = malloc_err; printf("%s\n", errorDesc(exit_code)); return 0; } 2.2. 普通函数的返回值 执行结果:文件打开成功,fopen()返回指向该文件的指针;文件打开失败,fopen()返回NULL。 执行情况:返回了非空的FILE*指针,则可认为函数执行成功;返回了NULL,则可认为函数执行失败,需要进一步检查错误的原因(errno变量或调用perror()函数)。 普通函数退出,仅仅表示函数调用完毕。 函数也被称为子程序,与进程退出时返回退出码类似,函数执行完毕也会返回一个值,这个值通常用于表示函数的执行结果或状态。 调用函数,我们通常想看到两种结果:a.函数的执行结果(函数的返回值);b.函数的执行情况(函数是否成功执行了预期的任务),例如:fopen()函数的执行情况是通过其执行结果来间接表示。 2.2.1. 错误码 errno是错误码,它是记录系统最后一次错误代码的一个整数值,不同值表示不同含义,在#include<errno.h>中定义。 当函数运行成功时,errno值不会被修改,因此我们不能通过测试errno的值来判断是否有错误存在,而应该在被调用的函数提示有错误发生时,再检查errno的值。 💡Tips:只有当库函数失败时,errno的值才会被设置。 #include<stdio.h> #include<string.h> #include<errno.h> int main() { FILE* fp = fopen("log.txt","r"); if(fp == NULL) printf("%d:%s\n", errno, strerror(errno)); return 0; } 2.3. 进程退出的场景 退出码用于表示程序的执行结果或者终止的原因。 退出信号(kill)用于指示进程是正常退出,还是被信号杀死(每个信号都有不同的编号,编号表明异常的原因)。 代码执行完毕,结果正确 -> 退出信号=0、退出码=0。 代码执行完毕,结果不正确 -> 退出信号=0、退出码=!0。 代码没有执行完毕,进程异常终止 -> 退出信号=!0、退出码无意义。 💡Tips:任何进程最终的执行情况,我们都可以用两个数字来表明具体的执行情况,这两个数字分别为退出码、退出信号。 2.4. 进程退出的方式 2.4.1. main函数的返回 当程序执行到main函数的末尾,或者遇到return语句时,程序会返回main函数的返回值(退出码)。 mian函数返回是程序主动退出的方式,即:正常终止进程。 2.4.2. 调用exit()、_exit()函数 一、exit()函数 void exit(int status); 调用exit()是程序主动退出的方式,即:正常终止进程。 exit()函数是C标准库提供的一个函数,在#include<stdlib.h>中定义,用于立即终止当前进程的执行,它会接受一个整形作为参数,该整形为进程的退出码。 调用exit(),程序会立即终止,exit()同时会执行清理操作(如:刷新所有的输出缓冲区、关闭通过fopen打开的文件、malloc开辟的内存等),然后向操作系统返回退出码。 二、_exit()函数 void _exit(int status); 调用_exit()是程序主动退出的方式,即:正常终止进程。 _exit()函数是系统调用函数,在#include<unistd.h>中定义,用于立即终止当前进程的执行,它会接受一个整形作为参数,该整形为进程的退出码。 _exit()不会自动执行exit()函数所执行的清理工作,需要确保在调用它之前,手动处理所有必要的清理工作,然后向操作系统返回退出码。 💡注意:main函数返回、调用exit()、_exit()函数,都表示程序主动退出,即:正常终止;接受到信号(如:ctrl c,信号终止),表示程序被动退出,即:异常退出。 2.4.3. exit()、_exit()函数的区别 exit()支持刷新缓冲区,_exit()不支持刷新缓冲区,因为exit中有缓存区,_exit中无缓冲区。 它们都是终止进程,但只有OS才有能力终止进程,因此exit()底层封装了_exit(),两者是上下层关系,。 💡Tips:我们之前谈及的缓冲区(进度条之类),绝对不是操作系统级别的缓冲区,是标准C库管理的缓冲区(库级别的缓冲区)。 为什么语言具有可移植性和跨平台性?在库层面上,对系统强相关的接口进行了封装,从而屏蔽了底层差异。 3. 进程等待 3.1. 必要性 子进程先退出,如果父进程不回收其资源,子进程就变成僵尸状态,此状态无法被kill -9杀死,因为无法杀死已经死掉的进程,从而造成内存泄漏。 父进程需要知道派给子进程的任务完成的如何,即:获取子进程的退出信息。 子进程的退出信息(exit code、exit signal),需要通过内核数据结构来维护,保存在子进程的task_struct中,属于内核数据。 💡Tips:总结:父进程通过进程等待的方式,回收子进程资源(必然),获取子进程的退出信息(可选)。 3.2. 方式 3.2.1. wait pid_t wait(int* status); 功能:等待任意一个子进程结束,并回收其资源。 返回值:调用成功,返回已经结束进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因。 参数status:输出型参数,用于存储子进程的退出状态,由OS填充,如果不需要这个信息,可以传递NULL,否则,OS会根据该参数,将子进程的信息反馈给父进程。 3.2.2. waitpid pid_t waitpid(pid_t pid, int* status, int options); 功能:等待任意一个子进程或者指定的子进程结束,并回收其资源。 参数pid:如果pid = -1,等待任意一个子进程,与wait等效;如果pid > 0,等待其进程的PID与pid相等的子进程。 参数option:如果option = 0,则为阻塞等待;如果option = WNOHANG,则为非阻塞等待。 返回值:调用成功,返回收集到的子进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因;如果为非阻塞等待,waitpid调用成功且没有收集到已结束的子进程,则返回0。 3.3. 阻塞等待、非阻塞状态 一、阻塞等待 定义:进程在发出某个请求(如:I/O操作、等待某个条件成立等)后,如果请求不能立即得到满足(如:数据未准备好、资源被占用等),进程会被挂起,在此期间无法继续执行其他任务,直到等待条件满足或被唤醒。 故事理解:张三找李四辅导c语言,张三打电话给李四叫他下来,但李四正在寝室中复习期末,李四在复习的期间,和张三一直通着电话,张三不能做任何其他事情。打电话的过程 == 系统调用。 特点: a.行为 -> 进程在等待期间无法执行其他任务。 b.触发方式 -> 等待由外部条件触发(如:数据到达、资源释放等)。 c.管理层面:由操作系统或者底层系统资源管理。 d.效率与并发性:效率低。 应用场景:实时性要求不高,等待时间相对比较短的情况,如:简单文件的读写操作。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); //父进程等待 —— 阻塞等待 if(rid > 0) //等待成功 printf("wait success, status:%d\n", status); else if(rid == -1) //调用失败 perror("wait error!\n"); return 0; } 二、非阻塞等待 定义:进程在发出某个请求后,不会被立即挂起已等待请求的完成,即使请求不能立即得到满足,进程在等待期间可以继续执行其他任务,同时可能会以某种方式(轮询访问、回调等)定期检查请求状态或者等待结果的通知。 故事理解:张三找李四辅导c语言,张三打电话给李四叫他下来,但李四正在寝室中复习期末,电话挂断,李四在复习的期间,张三可以做其他的事情,并定期多次打电话给李四,询问是否复习完毕。 特点: a.行为 -> 进程在等待期间可以执行其他任务; b.触发方式 -> 可能通过编程的方式实现,如:轮询、回调等。 c.管理层面:在应用层通过编程实现。 d.效率与并发性:效率高,提高并发性和响应能力。 应用场景:需要高并发和响应能力的场景,如:在网络编程中,服务器同时处理多个客户端的请求。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> #define SIZE 5 typedef void(*fun_t)(); //函数指针类型 fun_t task[SIZE]; //函数指针数组 void printlog() { printf("this is a log print task\n"); } void printnet() { printf("this is a net task\n"); } void printNPC() { printf("this is a flush NPC task\n"); } void Inittask() { task[0] = printlog; task[1] = printnet; task[2] = printNPC; task[3] = NULL; } void executeTask() { for(int i = 0; task[i]; i++) task[i](); //回调函数机制 } int main() { Inittask(); pid_t id = fork(); if(id == 0) //子进程 { int cnt = 2; while(cnt) { printf("I am a process, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 while(1) //基于非阻塞轮询的访问 { pid_t rid = waitpid(id, &status, WNOHANG); //非阻塞等待 if(rid > 0) //调用成功,收集到了已经结束的子进程 { printf("wait success, status:%d\n", status); break; } else if(rid == 0) //调用成功,未收集到已经结束的子进程 { printf("child is running, father do other thing!\n"); printf("------------ Task begin ----------------\n"); executeTask(); //等待期间,执行其他任务 printf("------------ Task end ----------------\n"); } else //调用失败 { perror("wait error\n"); break; } sleep(1); } return 0; } 3.4. 获取子进程 status不能简单的当作整形来看,可以当作位图看待,它有自己的格式,只研究status低16位比特位。 3.4.1. 位操作 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); if(rid > 0) //等待成功 printf("wait success, status:%d, exit code:%d, exit sign:%d\n", status, (status>>8)&0xff, status&0x7f); //位操作获取子进程的退出码、退出信号 return 0; } 3.4.2. 宏 WIFEXITED(status):检查子进程是否正常退出。 如果子进程通过调用exit函数或main函数return返回而退出,则WIFEXITED返回非0值(真)-》正常退出;如果子进程是由于接收到信号而退出,则WIFEXITED返回0(假)-》异常退出。 WEXITSTATUS(status):只有当WIFEXITED为真时,接着才会使用WEXITSTATUS获取子进程的退出码。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<sys/wait.h> int main() { pid_t id = fork(); if(id == 0) //子进程 { int cnt = 5; while(cnt) { printf("child is running, id:%d, ppid:%d\n", getpid(), getppid()); sleep(1); cnt--; } exit(1); //子进程退出 } int status = 0; //存储子进程退出状态 pid_t rid = waitpid(id, &status, 0); if(rid > 0) //等待成功 { if(WIFEXITED(status)) //子进程正常退出 printf("wait success, status:%d, exit code:%d\n", status, WEXITSTATUS(status)); //提取退出码 宏 else //子进程异常退出 printf("child process error!\n"); } return 0; } 问题1:在父进程中定义两个全局变量(exit code、exit sign),子进程修改exit code值,父进程可以获取到子进程的退出信息吗? 不能。因为进程具有独立性,子进程对共享数据的修改,父进程是不可见的。 问题2:为什么要有wait、waitpid? 为了避免子进程僵尸,造成内存泄漏,父进程需要通过wait、waitpid等函数来回收子进程资源,同时可以获取到子进程的退出信息。 子进程的退出码、退出信号等内核数据,需要被拷贝到用户层的某个变量(如:wait、waitpid中的status参数等),这个过程需要调用系统调用接口,因为用户空间的程序无法直接访问内核空间的数据。 4. 进程程序替换 4.1. 概念与原理 概念:它允许一个进程在执行期间,用一个新的程序来替换当前正常执行的程序,即:用全新的程序替换原有的程序。 这意味着进程在调用一种exec函数,当前进程的用户空间代码和数据被新程序的代码和数据完全替换(覆盖),从新程序的启动例程开始执行。 💡Tips:调用exec函数,并不会创建新的进程,而是对原有进程的资源进行替换,因此调用exec前后该进程的pid并未发生改变。 原理:加载新程序 -> 替换当前程序 -> 更新页表 -> 执行新程序。 加载新程序:当进程决定进行程序替换时(调用exec函数),它会请求OS将全新程序(代码和数据)从磁盘中加载到内存。 更新页表:为了实现替换,OS需要更新页表,将原来指向旧程序代码的虚拟地址映射到新程序代码的物理地址上,这样,就会执行新程序的代码。 所谓的把磁盘的数据加载到内存,把磁盘的数据拷贝到内存中,磁盘,内存都是硬件,只有操作系统具有将数据从一个硬件(磁盘)搬移到另一个硬件(内存)的能力,从而支持程序的加载和替换。 💡注意:进程替换的本质工作就是加载,充当的是加载器的角色! 4.2. 替换函数exec* l(list):有l表示命令行参数采用列表;v(vector):有v表示命令行参数采用数组; p(path):有p自动去环境变量中搜索,允许只写程序名); e(env):有e表示设置全新的环境变量,需要自己维护。 int execl(const char* path,const char* arg,. . .); 解释:以列表的形式传递命令行参数,最后必须以NULL结尾,第一个参数必须是程序的绝对路径。 #include<stdio.h> #include<unistd.h> int main() { printf("I am a process\n"); //以列表的形式传参(l); 命令行怎么写,参数怎么传(可变参数列表),但结尾必须以NULL结尾 execl("/usr/bin/ls", "ls", "-l", NULL); return 0; } int execv(const char* path,char* const argv[ ]); 解释:以数组的形式传递命令行参数,将命令行参数存储在以NULL指针结尾的指针数组中。 char* const argv[] = {(char*)"ls", (char*)"-l", NULL}; //存储命令行参数的以NULL结尾的指针数组 //以数组的形式传参(v) execv("/usr/bin/ls", argv); int execlp(const char* file,const char* arg,. . .); int execvp(const char* file,char* const argv[ ]); 解释:第一个参数允许用户只提提供程序名,在替换时,自动去环境变量PATH指定的路径中查找程序。 char* const argv[] = {(char*)"ls", (char*)"-l", NULL}; //存储命令行参数的以NULL结尾的指针数组 //有p自动去环境PATH中搜索,允许第一个参数只写程序名 execlp("bin", "ls", "-l", "NULL"); execvp("bin", argv); int execle(const char* path,const char* arg,. . . ,char* const envp[ ]); int execvpe(const char* file,char* const argv[ ],char* const envp[ ]); int execve(const char* filename,char* const argv[ ],char* const envp[ ]); 解释:最后一个参数为自己维护的环境变量(可设置全新的环境变量表)。 #include<stdio.h> #include<unistd.h> int main() { printf("I am a process\n"); char* const env[] = {(char*)"haha=hehe", (char*)"zzx=lala", NULL}; //自己维护环境变量 execle("./mytest", "-a", "-b", NULL, env); //设置全新的环境变量 return 0; } #include<stdio.h> int main(int argc, char* argv[], char* env[]) { for(int i = 0; argv[i]; i++) printf("argv[%d]:%s\n", i, argv[i]); for(int i = 0; env[i]; i++) printf("env[%d]:%s\n", i, env[i]); return 0; } 4.2.1. 细节 替换完成,不会创建新的进程。 程序一旦替换成功,exec*后续的代码不在执行。 exec*函数只有出错的返回值,没有成功的返回值。 exec*函数调用失败,返回-1,并设置错误码以指示错误的原因。 进程替换本身不会改变环境变量的数据。 子进程会自动继承父进程的环境变量,子进程通过exec函数进行程序替换,加载并执行新的程序,这个新的程序会继承子进程所拥有的环境变量。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/m0_74808907/article/details/141950474
-
前言: 本文介绍的是有关地址空间,咱们的介绍的大体思路是,先直接看现象,通过现象我们引入地址空间的概念,然后是简单的介绍地址空间,如何理解地址空间等,在往后的学习中,我们大概会介绍地址空间3 - 4次,因为地址空间有很多内容一次性还介绍不完,并且在本文中,我们能够理解之前颠覆代码三观的函数——fork(),现在就进入正题。 代码现象 目前我们对于地址空间没有明确的概念,所以先来看这样一段代码: #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include<sys/types.h> int g_val =100; int main() { pid_t id = fork(); if(id == 0) { int count = 1; while(count) { printf("g_val is %d,&g_val = %p\n",g_val,&g_val); sleep(1); if(count == 5) g_val = 200; count++; } } else { while(1) { printf("g_val is %d,&g_val = %p\n",g_val,&g_val); sleep(1); } } return 0; } 代码的意思是,我们创建一个父进程之后,在父进程里面创建一个子进程,子进程要完成的工作是打印g_val和它的地址,当count到5的时候就修改g_val,但是后续还是要一直打印,父进程要做的工作就是一直打印g_val和g_val的地址。 现象如下: 打印5秒之后,g_val的值如愿以偿的被修改了,此时让父进程打印的时候,我们发现一个怪事,打印的时候为什么父进程中的g_val没有变化呢?我们在进程部分知道父进程的数据是和子进程共享的,但是此时,父进程的数据被子进程修改了,父进程居然无动于衷? 现在的现象就是:一个变量,地址没有变化,但是拥有两个不同的值。 我们一会儿要理解的就是该现象,该现象理解了之后,我们同时就能理解fork函数的返回值是怎么回事了。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_79697943/article/details/142466903
上滑加载中
推荐直播
-
全面解析华为云EI-API服务:理论基础与实践应用指南
2024/11/29 周五 18:20-20:20
Alex 华为云学堂技术讲师
本期直播给大家带来的是理论与实践结合的华为云EI-API的服务介绍。从“主要功能,应用场景,实践案例,调用流程”四个维度来深入解析“语音交互API,文字识别API,自然语言处理API,图像识别API及图像搜索API”五大场景下API服务,同时结合实验,来加深开发者对API服务理解。
去报名 -
昇腾云服务ModelArts深度解析:理论基础与实践应用指南
2024/12/03 周二 14:30-16:30
Alex 华为云学堂技术讲师
如何快速创建和部署模型,管理全周期AI工作流呢?本期直播聚焦华为昇腾云服务ModelArts一站式AI开发平台功能介绍,同时结合基于ModelArts 的实践性实验,帮助开发者从理论到实验更好地理解和使用ModelArts。
去报名 -
深度解析鸿蒙应用入门级开发者认证
2024/12/04 周三 16:00-18:00
Edi 华为云学堂技术讲师
本期直播将为开发者带来HCCDA-HarmonyOS&Cloud Apps认证课程系统介绍、详细阐述HarmonyOS 技术架构、理解HarmonyOS 技术理念,通过实例带领开发者应用快速上手。
去报名
热门标签