-
【Linux】进程字段、环境变量与进程地址空间
一、查看进程字段 在Linux下,可以使用ps指令显示当前系统运行的进程信息,包含进程状态、资源使用情况等内容ps的-l选项可以显示长格式信息,包括F(标志)、S(状态)、UID、PID等详细字段: F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 1.字段说明 下面是对字段的解释说明: 字段 含义 F 标志位(Flags)。与进程状态和调度相关的标志,常见值: - 0:普通进程,通常是非前台组成员的用户进程 - 1:前台进程组成员,通常是终端运行的交互式程序 - 4:由超级用户(root 用户)创建的进程 - 10:系统不可杀的进程,如核心守护进程 S 进程状态(State)。表示进程当前的状态,可能的值: - R:运行(Runnable) - S:睡眠(Sleeping) - D:不可中断睡眠(Uninterruptible Sleep) - Z:僵尸进程(Zombie) - T:停止(Stopped) UID 用户 ID(User ID)。表示运行此进程的用户的 ID,即执行者的身份 - 0:超级用户(root),拥有最高权限 - 1-99:通常保留给系统用户(例如 daemon、bin) - 1000 及以上:普通用户 PID 进程 ID(Process ID)系统中每个运行进程的唯一标识符 PPID 父进程 ID(Parent Process ID)表示创建该进程的父进程的 PID C CPU 使用率(CPU utilization)表示进程使用 CPU 的相对时间(以百分比表示) PRI 优先级(Priority)进程当前的调度优先级,数值越低优先级越高 NI Nice 值(Niceness)影响进程优先级的用户调整值,范围为 -20(最高优先级)到 19(最低优先级) ADDR 内存地址(memory address)与进程的内存映射相关,通常显示为 -,表示不适用 SZ 内存大小(Size)表示进程的虚拟内存大小(以页为单位) WCHAN 等待通道(Wait Channel)如果进程正在等待资源或事件,则显示等待的内核函数名,否则为 - TTY 终端(Terminal)表示与进程关联的终端设备名 - 如果是后台进程……
SE_Wang 2024-12-13
89 0 0 -
【Linux | 计网】TCP协议深度解析:从连接管理到流量控制与滑动窗口
1、三次握手和四次挥手的联系: 其实四次挥手本质上与三次握手的流程一样的。此话怎讲? 三次握手的本质,其实也是4次握手,只不过中间两次被合并了(ACK和FIN合并了)! 为什么挥手必须要将ACK和FIN分开呢? 挥手的时候可以将ACK和FIN一起发送吗 在三次握手的时候,可以直接将SYN和ACK进行合并发送, 但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。 只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。 客户端想要断开连接发送FIN,服务器收到之后发送ACK应答,但是服务器在大多数情况下是不会立刻断开连接,因为可能还有业务没有处理完,所以ACK和FIN之间一定有一个时间差,这就导致了ACK不能和FIN一起发送 我们在挥手流程图有这样几个状态,分别是 TIME_WAIT , CLOSE_WAIT,FIN_WAIT。 2.理解 CLOSE_WAIT 状态 CLOSE_WAIT 状态存在的意义: CLOSE_WAIT是连接被动关闭方在收到对方发送的FIN请求后,发送ACK确认进入的状态。在这个状态下,应用程序可能还有未处理的数据需要发送,因此需要等待应用程序处理完这些数据后,才能发送FIN请求来关闭连接。如果应用程序没有及时关闭连接,可能会导致大量的CLOSE_WAIT状态,从而消耗系统资源。CLOSE_WAIT状态表示被动关闭方正在等待关闭,没有真正关闭。 CLOSE_WAIT状态的特点 双向关闭的一部分:TCP连接是双向的,需要两端都同意关闭连接才能完全关闭。CLOSE_WAIT状态表示本地端(即接收ACK的一端)已经收到了对方的关闭请求,并发送了确认,但还在等待本地端的应用程序或系统发送关闭请求以完成连接的关闭。 等待远程FIN报文:在CLOSE_WAIT状态下,本地端会等待远程端发送FIN报文来关闭连接。如果远程端没有发送FIN报文……
SE_Wang 2024-12-12
121 0 0 -
【Linux】匿名管道通信场景——进程池
1. 初始化进程池 进程池的实现是依靠匿名管道,通过进程间通信使得父进程能够管理多个进程任务,相当于父进程拥有了很多个进程——进程池,通过不同的进程完成指定的任务。 所以我们需要创建多个匿名管道和子进程,进行进程间通信,发送信息给子进程让它们根据接收到的信息处理相关任务。 因为有多个管道和子进程,为了方便父进程使用不同管道发送对应信息给子进程,我们需要将管道的文件描述符以及对应子进程的pid保存起来,我们选择将它们封装在一个Channel类中。又因为有多个匿名管道和子进程,所以将多个Channel类对象储存在C++STL中的容器vector中来方便父进程进行管理进程池。 代码如下: int InitProcesspool(int num,std::vector<Channel>& channels) { for(int i = 0; i < num; i++)//使用循环创建多个匿名管道和子进程 { //1.创建匿名管道 int pipefd[2] = {0}; int n = pipe(pipefd); if(n < 0) return 2;//根据不同的返回值判断原因,也可以使用枚举来约定返回值代表的内容 //2.创建子进程 pid_t id = fork(); if(id < 0) return 3; //3.建立通信管道,父子进程关闭读端或写端 if(id == 0)//子进程 { //子进程读取,关闭写端 ::close(pipefd[1]); //dup2 dup2(pipefd[0],0); //子进程需要执行的内容 Work(); ::exit(0); } //父进程 //父进程写入,关闭读端 ::close(pipefd[0]); channels.emplace_back(pipefd[1],id);//保存在channel对象中并存入vector } return 0; } 对子进程内部,我们使用dup2系统调用将匿名管道读端文件描述符与标准输入stdin交换,这样我们就不需要保存不同进程对应匿名管道的读端文件描述符,只需要统一从0号文件描述符中读取内容即可。 对于Channel类: class Channel{ public: Channel(int fd,pid_t who):_fd(fd),_who(who) { _name = "Channel-"+std::to……
SE_Wang 2024-12-11
136 0 0 -
Linux之网络编程(UDP)
第1关:UDP套接字创建与端口绑定 #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <errno.h> #include <string.h> /************************ * port: 需要绑定的端口号 * 返回值: 调用成功返回0,否则返回-1 *************************/ int UDPSocket(unsigned short port) { int ret = -1; /********** BEGIN **********/ int sockfd; struct sockaddr_in server_addr; // 创建UDP套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { printf("创建UDP套接字失败: %s\n", strerror(errno)); return -1; } // 初始化服务器地址结构体 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(port); // 绑定端口 ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (ret == -1) { printf("绑定端口失败: %s\n", strerror(errno)); close(sockfd); return -1; } ret = 0; /********** END **********/ return ret; } 第2关:UDP数据传送 #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #define PORT 8888 int main() { int sockfd; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd == -1) { return -1; } struct sockaddr_in addr; bzero(&addr, sizeof(addr)); //清空 addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = htonl(INADDR_ANY); //与PO……
SE_Wang 2024-12-10
63 0 0 -
【Linux】线程安全与锁概念——自旋锁、读写锁
一:🔥 线程安全和重⼊问题 线程安全:就是多个线程在访问共享资源时,能够正确地执⾏,不会相互⼲扰或破坏彼此的执⾏结果,也不会出现数据不一致的问题。⼀般⽽⾔,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进⾏操作,并且没有锁保护的情况下,容易出现该问题。 重⼊:同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊,我们称之为重⼊。⼀个函数在重⼊的情况下,运⾏结果不会出现任何不同或者任何问题,则该函数被称为可重⼊函数,否则,是不可重⼊函数。 🍥 学到现在,其实我们已经能理解重⼊其实可以分为两种情况 多线程重⼊函数。 信号导致⼀个执⾏流重复进⼊函数。 结论 : 不要被上⾯绕⼝令式的话语唬住,你只要仔细观察,其实对应概念说的都是⼀回事。 🦋 可重⼊与线程安全联系 🍡 函数是可重⼊的,那就是线程安全的 (其实知道这⼀句话就够了) : 🍡 函数是不可重⼊的,那就不能由多个线程使⽤,有可能引发线程安全问题 🍡 如果⼀个函数中有全局变量,那么这个函数既不是线程安全也不是可重⼊的。 🦋 可重⼊与线程安全区别 🍡 可重⼊函数是线程安全函数的⼀种 🍡 线程安全不⼀定是可重⼊的,⽽可重⼊函数则⼀定是线程安全的。 🍡 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重⼊函数若锁还未释放则会产⽣死锁,因此是不可重⼊的。 📌 注意: 如果不考虑 信号导致⼀个执⾏流重复进⼊函数 这种重⼊情况,线程安全和重⼊在安全⻆度不做区分 但是线程安全侧重说明线程访问公共资源的安全情况,表现的是 并发线程的特点 可重⼊描述的是⼀个函数是否能被重复进⼊,表⽰的是 函数的特点 二:🔥 常⻅锁概念 🦋 2-1 死锁 死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程……
SE_Wang 2024-12-09
165 0 0 -
自己构建服务器 | 实现网页分离 | 设计思路
一. 最简单的HTTP服务器 基于上一篇文章的理论: 我们可以尝试实现一个简单的 HTTP 服务器,它可以接受客户端连接并返回一个 "Hello World" 网页。为了详细说明这段代码,让我们逐行进行解释。 #include <sys/socket.h> // 引入套接字相关的头文件 #include <netinet/in.h> // 引入处理IPv4地址的头文件 #include <arpa/inet.h> // 引入INET相关函数的头文件 #include <unistd.h> // 引入UNIX标准函数,如close() #include <stdio.h> // 引入标准输入输出头文件 #include <string.h> // 引入字符串处理函数的头文件 #include <stdlib.h> // 引入标准库函数,如atoi() 这些头文件包含了程序所需的各种函数和类型: sys/socket.h: 提供套接字函数和数据结构。 netinet/in.h: 提供了用于处理 IPv4 地址的结构和函数。 arpa/inet.h: 提供了用于操作 IP 地址的函数,如 inet_addr。 unistd.h: 提供了 UNIX 标准函数,如 close。 stdlib.h: 提供了一些标准库函数,如 atoi。 void Usage() { printf("usage: ./server [ip] [port]\n"); } 定义了一个 Usage 函数,该函数打印使用说明,说明程序需要两个命令行参数,即 IP 地址和端口号。 int main(int argc, char* argv[]) { 程序的 main 函数开始。 if (argc != 3) { Usage(); return 1; } 检查命令行参数的数量。如果参数数量不等于 3(程序名、IP 地址和端口号) int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); // 如果创建失败,打印错误信息 return 1; } 创建一个套接字。AF_INET 表示使用 IPv4,SOCK_STREAM 表示使用 TCP。 struct sockaddr_in addr; // 定义一个地址结构体 addr.sin_family = AF_INET; // 设置为IPv4地址族 addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置IP地……
SE_Wang 2024-12-06
134 0 0 -
【Linux探索学习】第十六弹——进程地址空间:深入解析操作系统中的进程地址空间
一、什么是进程地址空间? 进程地址空间是操作系统为每个进程分配的一块独立的虚拟地址范围,用于存储程序代码、数据和栈等运行所需的内容。操作系统通过虚拟内存技术,使每个进程拥有一个独立的地址空间,与物理内存隔离。 1.1 进程地址空间的特点 虚拟化:每个进程的地址空间是独立的虚拟地址,互不干扰。 隔离性:一个进程不能直接访问另一个进程的地址空间,提供了安全性。 统一性:操作系统通过页表将虚拟地址映射到物理地址,对用户透明。 二、进程地址空间的结构 操作系统将进程地址空间划分为多个区域,每个区域用于存储特定类型的数据。以下是典型的地址空间布局: 地址区域 描述 代码段 存储可执行代码的指令。只读,通常不可修改。 数据段 存储已初始化的全局变量和静态变量。 BSS段 存储未初始化的全局变量和静态变量。 堆(Heap) 动态分配的内存区域(如malloc分配的内存)。向高地址增长。 栈(Stack) 函数调用相关的局部变量、返回地址等。向低地址增长。 内核空间 操作系统内核相关的代码和数据。用户态无法直接访问。 2.1 地址空间布局图 以32位操作系统为例,地址空间布局如下: +---------------------------+ 0xFFFFFFFF | 内核空间 | +---------------------------+ 0xC0000000 | 用户栈 | +---------------------------+ | 动态分配的堆(Heap) | +---------------------------+ | BSS段 | +---------------------------+ | 数据段 | +---------------------------+ | 代码段 | +---------------------------+ 0x00000000 三、各段的详细说明 3.1 代码段 存储内容:存放程序的可执行代码。 访问权限:只读,防止程序意外修改指令。 特点:多个进程可以共享同一段代码段(如共享库)。 3.2 数据段 存储内容:存储已初始化的全局变量和静态变量。 访问权限:读写权限。 特点:程序运行时……
SE_Wang 2024-12-05
85 0 0 -
Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(1)
1.Linux的背景介绍 Linux 操作系统的发展历程充满了激情与创新喵~🎀 萌芽期 (1983 - 1991):Linux 的历史可追溯到 1983 年,理查德·斯托曼 (Richard Stallman) 发起 GNU 计划,目标是创建一个自由软件操作系统。1987 年发布的 MINIX 是一个小型 UNIX 系统,为 Linux 提供了灵感。 诞生 (1991):1991 年,芬兰大学生林纳斯·托瓦兹 (Linus Torvalds) 开始编写自己的内核并将其命名为 Linux。他在网上发布了源代码,许多人加入到改进和完善的过程中。 快速发展 (1992 - 2000):1992 年,Linux 内核采用 GPL 开源许可证。越来越多的开发者参与其中,Linux 变得更加稳定,丰富的社区支持推动了软件包和驱动的开发。1994 年,发布了第一个成熟的 Linux 版本 (Linux 1.0)。 企业应用 (2000 - 2010):随着开源社区的壮大,Linux 开始受到企业青睐,特别是服务器领域。许多公司,如 Red Hat、SUSE 等,推出了商业版本的 Linux 操作系统,用于服务器、企业系统。 现代化 (2010 - 现在):Linux 已经成为全球服务器市场和云计算的主流选择,同时也是 Android 系统的核心。Linux 被广泛应用于嵌入式系统、物联网、超级计算机等领域,甚至在桌面系统上也逐渐受到欢迎。 Linux 的发展历程是一个开源社区、技术创新和用户需求相结合的传奇故事,它已成为全球最重要的操作系统之一 发行版本: 我主要使用 XShell 远程登录 Linux 下载安装 XShell:下载链接 小技巧 : 2.ls 指令 ls:当前目录下的所有 普通文件和子目录 语法: ls [选项][目录或文件] 1.且看第一行中的" ls -l"(" "空格不能省略!!!!!!) 2. 其中,ls是命令 3. 而 -l是选项 功能:(1)对于目录,该命令列出该目录下的所有 子目录 与 文件 。 (2)对于 文件,将列出 文件名 以及其他信息 3.常用选项(目前): -a 列出目录下的所有文件,包括以 . 开头的 隐含……
SE_Wang 2024-12-04
93 0 0 -
Linux下学【MySQL】表的必备操作
表的操作 对于表的操作需要注意的是: 不要轻易的去修改表的名字,类型,结构 因为数据库表是在比较底层的,当修改了表后,上层代码很大可能会发生问题! 1.创建表 语法: CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎; 具体使用: 1. create table if not exists user1( id int, name varchar(20) comment '用户名', password char(32) comment '密码', birthday date comment '生日' )character set utf8 collate utf8_general_ci engine MyIsam; 其中 comment 是该名称的别名,方便查看 if not exists 和库的一样,判断是否有存在 2. create table user2( id int, name varchar(20) comment '用户', password char(32) comment '密码', birthday date comment '生日' )charset=utf8 collate=utf8_general_ci engine=InnoDB; 对比两次创表过程:最后的字符集和校验规则以及存储引擎 有两种自定义的方法: character set utf8 collate utf8_general_ci engine MyIsam; charset=utf8 collate=utf8_general_ci engine=InnoDB; 1.1 表的存储引擎 user1 表存储引擎是 MyISAM ,在数据目中有三个不同的文件,分别是: user1.frm:表结构 user1.MYD:表数据 user1.MYI:表索引 user2 表存储引擎是 InnoDB ,在数据目中有的文件是: user2.frm:表结构 user2.ibd:表空间文件,用于存储数据和索引 附: 当不指定写字符集和校验规则以及存储引擎的话就会默认成配置文件所默认的。配置文件所在的地址:/etc/my.cnf 1.2查看表 语法: show tables; 1.2.1 查看表结构(详细信息) 语法: desc 表名; 1.2.2 查看创建表的语句 语法: show create table 表名; show create table 表名 \G;#加上\G格式化展示 1.2.3 查看创建表中的……
SE_Wang 2024-12-03
111 0 0 -
【Linux】进程控制,手搓简洁版shell
1、进程创建 fork函数:从已经存在的进程中创建一个新进程。新进程为子进程,原进程为父进程。 进程调用fork,当控制转移到内核中的fork代码后,内核做: 分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝给子进程 添加子进程到系统进程列表当中 fork返回,开始调度器调度 写时拷贝 (懒拷贝,时间换空间) 数据在默认不修改的情况下是共享的,不各自拷贝一份是因为父子进程间的数据大部分是重复的,一般只有少量数据需要修改,因为各自拷贝一份浪费空间。 更新父进程页表项为只读—子进程继承—子进程写入—触发系统错误—系统触发缺页中断—系统检测—判定是否要写时拷贝—拷贝,修改,恢复权限。 创建出子进程,让子进程执行一些任务: #include <iostream> #include <vector> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> using namespace std; enum { OK, OPEN_FILE_ERROR }; vector<int> data; int savebegin() { string name = to_string((unsigned int)time(nullptr)); name += ".backup"; FILE *pf = fopen(name.c_str(), "w"); if (pf == nullptr) { return OPEN_FILE_ERROR; } string datastr; for (auto d : data) { datastr += to_string(d); datastr += " "; } fputs(datastr.c_str(), pf);//将拿到的数据备份到文件中 fclose(pf); return OK; } void save() { pid_t id = fork(); if (id == 0) { //子进程备份数据 int code = savebegin(); exit(code); } int status = 0; pid_t rid = waitpid(id, &status, 0); if (rid > 0) { int code = WEXITSTATUS(status);//进程退出码 if (code == 0) cout << "备份成功, exit code:" << code << endl; else cout << "……
SE_Wang 2024-12-02
80 0 0
