-
磐维数据库语法入门介绍一、引言磐维数据库(PanWeiDB)是中国移动信息技术中心首个基于中国本土开源数据库打造的面向ICT基础设施的自研数据库产品。其内核能力基于华为openGauss开源软件,并进一步提升了系统稳定性。对于数据库开发者和管理员来说,掌握磐维数据库的语法是进行数据库操作、管理和优化的基础。本文将详细介绍磐维数据库的基本语法,包括数据定义语言(DDL)、数据操作语言(DML)、数据控制语言(DCL)以及事务处理等,并提供相应的代码示例。二、数据定义语言(DDL)DDL用于定义和管理数据库结构,包括创建、修改和删除数据库对象,如数据库、表、索引等。1. 创建数据库与表-- 创建数据库 CREATE DATABASE mydb; -- 使用数据库 USE mydb; -- 创建表 CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); 在创建表时,可以指定列的数据类型、约束条件(如主键、唯一约束、非空约束等)以及默认值等。2. 修改表结构-- 添加列 ALTER TABLE users ADD age INT; -- 删除列 ALTER TABLE users DROP COLUMN email; -- 修改列数据类型或约束 ALTER TABLE users MODIFY COLUMN username VARCHAR(100); 通过ALTER TABLE语句,可以在不删除表的情况下修改表的结构,如添加新列、删除列或修改列的数据类型或约束条件。3. 删除数据库与表-- 删除表 DROP TABLE users; -- 删除数据库 DROP DATABASE mydb; 当不再需要某个表或数据库时,可以使用DROP TABLE或DROP DATABASE语句将其删除。三、数据操作语言(DML)DML用于对数据库中的数据进行插入、查询、更新和删除等操作。1. 插入数据-- 插入数据 INSERT INTO users (id, username, age) VALUES (1, 'alice', 30); 通过INSERT INTO语句,可以向表中插入新的数据行。在插入数据时,需要指定要插入的列和对应的值。2. 查询数据-- 查询数据 SELECT id, username FROM users WHERE age > 25; 使用SELECT语句可以从表中查询数据。在查询时,可以指定要查询的列和查询条件(如WHERE子句)。3. 更新数据-- 更新数据 UPDATE users SET age = 31 WHERE id = 1; 通过UPDATE语句,可以修改表中已存在的数据行。在更新数据时,需要指定要更新的列、新的值以及更新条件(如WHERE子句)。4. 删除数据-- 删除数据 DELETE FROM users WHERE age < 20; 使用DELETE语句可以从表中删除数据行。在删除数据时,需要指定删除条件(如WHERE子句)。5. upsert语法upsert是磐维数据库中的一个重要功能,它允许DML语句在插入一行数据或者在现存行的基础上更新数据行。upsert相当于insert + update的组合,当数据行insert发生主键或唯一约束重复时,可触发update操作。Oracle风格的upsert语法:INSERT INTO table_name(column1, column2) VALUES(value1, value2) ON DUPLICATE KEY UPDATE column2 = EXCLUDED.column2; PG风格的upsert语法:INSERT INTO table_name(column1, column2) VALUES(value1, value2) ON CONFLICT(column1) DO UPDATE SET column2 = EXCLUDED.column2; 使用示例:-- 创建测试表 CREATE TABLE test_upsert ( id INT PRIMARY KEY, code VARCHAR UNIQUE, info VARCHAR ); -- 插入一条主键id为1的数据 INSERT INTO test_upsert (id, code, info) VALUES (1, 'code1', 'info'); -- 再次插入id为1的数据时使用upsert语句 INSERT INTO test_upsert (id, code, info) VALUES (1, 'code1', 'info extra1') ON CONFLICT(id) DO UPDATE SET info = EXCLUDED.info; 在上面的示例中,当尝试再次插入id为1的数据时,由于主键冲突,会触发update操作,将info列的值更新为’info extra1’。四、数据控制语言(DCL)DCL用于定义和管理数据库用户的权限,包括授予权限和撤销权限等。1. 授予权限-- 授予权限 GRANT SELECT, INSERT ON users TO user1; 通过GRANT语句,可以将指定的权限授予某个用户。在授予权限时,需要指定权限类型(如SELECT、INSERT、UPDATE、DELETE等)和对象(如表、视图等)以及用户。2. 撤销权限-- 撤销权限 REVOKE INSERT ON users FROM user1; 使用REVOKE语句可以撤销之前授予的权限。在撤销权限时,需要指定权限类型、对象和用户。五、事务处理事务是一组逻辑上相关的操作,这些操作要么全部执行成功,要么全部不执行。事务处理是数据库管理中的一个重要概念,它保证了数据的一致性和完整性。1. 开始事务-- 开始事务 START TRANSACTION; 通过START TRANSACTION语句可以显式地开始一个新的事务。在事务开始之后,所有对数据库的操作都将被记录在一个事务日志中,直到事务被提交或回滚。2. 提交事务-- 提交事务 COMMIT; 当事务中的所有操作都执行成功时,可以使用COMMIT语句将事务提交到数据库中。提交事务后,事务日志中的所有操作都将被永久保存到数据库中。3. 回滚事务-- 回滚事务 ROLLBACK; 如果在事务执行过程中发生错误或需要取消事务中的所有操作,可以使用ROLLBACK语句将事务回滚到开始状态。回滚事务后,事务日志中的所有操作都将被撤销,数据库将恢复到事务开始之前的状态。六、磐维数据库与MySQL的部分语法区别在迁移数据库时,了解目标数据库与源数据库的语法差异是非常重要的。以下是磐维数据库与MySQL在部分语法上的区别:加密/解密:MySQL:加密使用HEX(AES_ENCRYPT(BL_PHONES, '8NONwyJtHesysWpM')),解密使用AES_DECRYPT(UNHEX(BL_PHONES),'8NONwyJtHesysWpM') as BL_PHONES。磐维数据库:加密使用gs_encrypt(bl_phones,'秘钥','sm4'),解密使用gs_decrypt(bl_phones ,'秘钥','sm4')。时间函数:MySQL:使用now()函数获取当前时间。磐维数据库:now()函数可能不生效,可以使用sysdate或CURRENT_TIMESTAMP等替代。字符串函数:MySQL:UNHEX(name)函数用于将十六进制字符串转换为普通字符串。磐维数据库:UNHEX(name)函数可能不生效,需要寻找其他替代方法。查询count时带order by:MySQL:在查询count时,可以在SQL后面加上order by xxx desc。磐维数据库:不支持在查询count时后面带上order by,需要删除。cast函数:MySQL:cast(phone as CHAR)是将字段拆解为一个个字符重新拼起来,原数据不变,只是结果类型变了。磐维数据库:cast函数不是拆解成每个字符,而是取该数据的第一个字母/数字,并且遇到中文会报错。修改字段属性:MySQL:使用ALTER TABLE cm_system_resource_files CHANGE VERSION VER int;来修改字段属性。磐维数据库:修改字段属性的语法可能与MySQL不同,需要查阅相关文档。
-
Linux系统端口管理详解在Linux系统中,端口管理是网络配置和管理中不可或缺的一部分。了解如何有效地管理端口,不仅可以提高系统的网络性能,还能增强系统的安全性。本文将深入探讨Linux系统的端口管理,包括端口的基本概念、查看端口状态、开放和关闭端口、端口扫描、性能优化以及安全防护等方面的内容。一、端口的基本概念在Linux系统中,端口是网络通信的基础,它是操作系统提供的一种逻辑接口,用于区分不同类型的网络服务。端口号是一个16位的数字,其范围从0到65535。其中,0到1023号端口被系统保留,用于常见的网络服务,如HTTP(80端口)、HTTPS(443端口)、SSH(22端口)等。而1024到65535号端口则通常用于用户自定义的服务。二、查看端口状态在Linux系统中,有多种工具可以用来查看端口的状态,包括netstat、ss、lsof和nmap等。1. 使用netstat命令netstat是一个强大的工具,用于显示网络连接、路由表、接口统计信息等。要查看所有开放的TCP和UDP端口,可以使用以下命令:sudo netstat -tuln其中,-t显示TCP端口,-u显示UDP端口,-l显示监听中的端口,-n以数字形式显示地址和端口号。2. 使用ss命令ss(Socket Statistics)是另一个用于获取套接字统计信息的工具,与netstat类似,但在某些方面更高效。查看所有开放的TCP和UDP端口的命令如下:sudo ss -tuln3. 使用lsof命令lsof(List Open Files)命令可以列出当前系统打开的文件,包括网络端口。要查看所有监听的端口,可以使用以下命令:sudo lsof -i -P -n | grep LISTEN4. 使用nmap命令nmap是一个网络探测和安全审计工具,可以扫描本地或远程主机的开放端口。要扫描本地主机的所有端口,可以使用以下命令:sudo nmap -sT -O localhost其中,-sT表示进行TCP连接扫描,-O表示识别操作系统。三、开放和关闭端口在Linux系统中,开放和关闭端口通常通过配置防火墙来实现。常见的防火墙工具有iptables和firewalld。1. 使用iptablesiptables是Linux内核自带的防火墙工具,通过配置规则来控制网络流量的进出。开放端口要开放一个端口,可以使用以下命令:sudo iptables -A INPUT -p tcp --dport 端口号 -j ACCEPT例如,要开放80端口,可以使用:sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT关闭端口要关闭一个端口,可以使用以下命令:sudo iptables -D INPUT -p tcp --dport 端口号 -j ACCEPT例如,要关闭80端口,可以使用:sudo iptables -D INPUT -p tcp --dport 80 -j ACCEPT保存修改为了使防火墙规则在重启后仍然有效,需要将规则保存到配置文件中。对于不同的Linux发行版,配置文件的位置可能有所不同。在CentOS中,可以使用以下命令:service iptables save在Debian/Ubuntu中,可以使用以下命令:sudo apt-get install iptables-persistent sudo netfilter-persistent save2. 使用firewalldfirewalld是CentOS 7及以后版本默认使用的防火墙工具,提供了更友好的管理界面和动态更新功能。查看当前防火墙规则firewall-cmd --list-all开放端口要永久开放一个端口,可以使用以下命令:firewall-cmd --zone=public --add-port=端口号/tcp --permanent例如,要开放80端口,可以使用:firewall-cmd --zone=public --add-port=80/tcp --permanent关闭端口要永久关闭一个端口,可以使用以下命令:firewall-cmd --zone=public --remove-port=端口号/tcp --permanent例如,要关闭80端口,可以使用:firewall-cmd --zone=public --remove-port=80/tcp --permanent重新加载防火墙规则为了使更改生效,需要重新加载防火墙规则:firewall-cmd --reload四、端口扫描端口扫描是网络安全领域的一项重要技术,用于检测目标主机上开放的端口和服务。在Linux系统中,有多种工具可以用来进行端口扫描,包括nmap、Hping3和Masscan等。1. 使用nmap命令nmap是一个功能强大且灵活的工具,支持多种扫描技术,如SYN扫描、UDP扫描等。以下是一些常用的nmap命令示例:TCP SYN扫描nmap -sS 目标IPUDP扫描nmap -sU 目标IP服务版本扫描nmap -sV 目标IP2. 使用Hping3命令Hping3是一个网络工具,可以用于构造和发送自定义IP数据包,也可以用于扫描主机上的开放端口。以下是一个使用Hping3进行端口扫描的示例:sudo hping3 -S -p 端口号 目标IP --scan 端口范围3. 使用Masscan命令Masscan是一种高速、多线程的端口扫描工具,可以在短时间内扫描大量的端口。以下是一个使用Masscan进行端口扫描的示例:sudo masscan -p 端口范围 目标IP五、端口性能优化在Linux系统中,优化端口性能可以提高网络吞吐量和降低延迟。以下是一些常见的端口性能优化策略:1. 增加文件描述符限制文件描述符是操作系统为每个进程分配的一个资源,用于表示打开的文件或套接字。在Linux系统中,可以通过修改/etc/security/limits.conf文件来增加文件描述符限制:* soft nofile 65535 * hard nofile 65535 2. 调整内核参数Linux内核提供了许多参数来调整网络性能。例如,可以通过修改/etc/sysctl.conf文件来调整TCP连接的最大数量:net.ipv4.ip_local_port_range = 1024 65535 net.core.somaxconn = 65535 然后,使用以下命令使更改生效:sudo sysctl -p3. 使用高效的网络工具如前所述,ss工具在处理高负载系统时比netstat更高效。因此,在需要监控大量连接时,建议使用ss工具。六、端口安全防护在Linux系统中,保护开放的端口免受攻击是至关重要的。以下是一些常见的端口安全防护措施:1. 配置防火墙规则通过配置防火墙规则,可以限制只有特定的IP地址或端口能够访问特定的服务。例如,可以使用iptables或firewalld来配置防火墙规则。2. 使用强密码和认证机制对于需要用户认证的服务(如SSH、FTP等),应使用强密码和安全的认证机制(如公钥认证)。3. 定期更新软件和补丁定期更新系统和软件可以修复已知的安全漏洞,提高系统的安全性。4. 使用入侵检测系统(IDS)和入侵防御系统(IPS)IDS和IPS可以监控网络流量并检测潜在的安全威胁,及时采取措施防止攻击。七、总结Linux系统的端口管理是网络配置和管理中的重要组成部分。通过了解端口的基本概念、查看端口状态、开放和关闭端口、端口扫描、性能优化以及安全防护等方面的内容,可以有效地提高系统的网络性能和安全性。在实际操作中,应根据具体需求和环境选择合适的工具和策略来管理端口。同时,也应保持对新技术和新工具的关注和学习,以便更好地应对不断变化的网络安全挑战。
-
指令集设计理念X86 架构基于复杂指令集(CISC)理念设计,力求以单条指令完成复杂操作,通过丰富指令种类来减少程序中的指令总数,提升整体执行效率。例如,一条 X86 指令可直接实现内存到寄存器的复杂数据传输与运算,这在传统桌面和服务器应用场景中,可有效减少指令调用次数,优化多任务处理和大型数据库操作的性能。然而,这也导致指令集庞大复杂,指令长度从 1 字节到 15 字节不等,使得指令译码难度大增,硬件设计复杂度显著提升。ARM 架构遵循精简指令集(RISC)理念,指令功能简单且单一,仅执行基本操作,如简单的数据传输、算术运算等。这种设计使得指令能够快速执行,并且在硬件设计上更易于实现高度并行处理。在移动设备和嵌入式系统中,ARM 架构利用并行设计,可同时处理多个简单指令,实现高效数据处理。尽管单条指令功能有限,但凭借快速执行和并行机制,在特定领域表现出卓越的整体性能。指令格式X86 架构的指令格式极为复杂,由于指令长度可变,其操作码、操作数等字段的位置和长度不固定。这就要求指令译码依靠复杂的逻辑电路进行解析,增加了硬件设计的难度与成本。不同的指令格式适用于不同操作场景,如某些格式用于内存访问,某些用于寄存器操作,虽为编程提供了灵活性,但也要求开发者深入了解指令集,才能精准运用。ARM 架构的指令格式相对规整,指令长度固定为 32 位(ARMv8 及后续版本引入了 64 位指令集)。这种固定长度的指令格式使指令译码简单高效,在硬件设计时可简化译码电路,降低硬件成本。同时,ARM 指令格式的操作码、操作数等字段位置和长度相对固定,便于编译器优化,提高代码生成效率。例如,ARM 指令的操作码通常位于指令高位,操作数处于特定位置,这种清晰的结构有助于快速解析和执行指令。执行效率在执行效率方面,X86 架构在处理复杂任务时,因单条指令可完成较多工作,减少了指令间的切换开销,在多任务处理、大型数据库复杂查询等场景中具有优势。不过,由于指令复杂,译码和执行过程相对缓慢,在对实时性要求极高、需快速响应的场景下,可能无法满足需求。ARM 架构指令执行速度快,因为指令简单,译码和执行时间短。在移动设备和嵌入式系统中,数据处理通常有实时性要求,ARM 架构凭借快速的指令执行,能够高效完成任务。此外,ARM 架构采用高度并行设计,如超标量流水线技术,可同时执行多条指令,进一步提升整体执行效率。例如在图像识别的嵌入式应用中,ARM 处理器能快速处理大量图像数据,满足实时性需求。对硬件的要求X86 架构的复杂指令集致使硬件设计极为复杂。为实现复杂指令的译码和执行,需要大量晶体管和复杂的控制逻辑电路,这不仅增大了芯片面积,还提高了功耗。同时,复杂的指令集增加了处理器研发的难度和成本,因为要确保各种复杂指令的正确执行和兼容性。ARM 架构的精简指令集使硬件设计相对简单。由于指令格式固定、功能单一,所需的译码电路和控制逻辑相对简单,能够减少芯片上的晶体管数量,降低芯片面积和功耗。这使得 ARM 架构在移动设备、物联网设备等对功耗和成本敏感的领域具有显著优势。此外,简单的硬件设计还降低了研发成本,让更多厂商能够基于 ARM 架构进行芯片设计和开发。
-
在 Linux 主机的世界里,ARM 架构和 X86 架构犹如两颗璀璨的明星,各自闪耀着独特的光芒。它们在性能、功耗、成本以及应用场景等方面存在着显著的差异,这些差异也决定了它们在不同领域的广泛应用。性能表现指令集的力量X86 架构采用复杂指令集(CISC),指令长度可变,一条指令可以完成较为复杂的操作。这使得 X86 处理器在处理传统桌面和服务器应用时具有优势,例如多任务处理、大型数据库操作等。然而,复杂的指令集也带来了硬件设计的复杂性和较高的功耗。ARM 架构则基于精简指令集(RISC),指令长度固定,指令功能相对简单。这使得 ARM 处理器能够以较低的功耗运行,并且在一些特定的应用场景下,如移动设备和嵌入式系统,能够通过高度并行的设计实现高效的数据处理。虽然单条指令的功能不如 X86 指令强大,但通过指令的快速执行和并行处理,ARM 处理器在特定领域展现出了卓越的性能。核心设计与性能X86 架构的处理器通常具有较高的时钟频率,能够在单位时间内执行更多的指令。这使得它们在处理需要大量计算资源的任务时表现出色,例如图形渲染、科学计算等。此外,X86 处理器在多核设计方面也取得了显著进展,能够同时处理多个复杂的任务流。ARM 架构的处理器则更注重能效比,通过优化核心设计和采用先进的制程工艺,在较低的功耗下实现高效的运算。ARM 处理器的核心数量可以根据不同的应用需求进行灵活配置,从单核到多核甚至大规模的集群,能够满足从简单的嵌入式设备到高性能服务器的多样化需求。功耗管理ARM:低功耗的王者ARM 架构天生就是为低功耗而生的。其精简的指令集和高效的硬件设计使得 ARM 处理器在运行时能够以较低的电压和频率工作,从而大大降低了功耗。这一特性使得 ARM 架构在移动设备、物联网设备以及对功耗敏感的嵌入式系统中得到了广泛应用。例如,我们日常使用的智能手机和平板电脑,大多数都采用了 ARM 架构的处理器,以确保长时间的续航能力。X86:功耗挑战与应对相比之下,X86 架构的处理器由于复杂的指令集和较高的时钟频率,通常功耗较高。在传统的桌面和服务器应用中,这一问题可能并不突出,因为设备通常连接到电源插座,无需担心电池续航问题。然而,随着数据中心规模的不断扩大,功耗成为了一个重要的成本因素。为了应对这一挑战,X86 处理器厂商也在不断努力改进功耗管理技术,例如采用动态电压频率调整(DVFS)技术,根据处理器的负载情况动态调整电压和频率,以降低功耗。硬件成本ARM:低成本的优势ARM 架构的处理器通常采用授权模式,厂商可以根据自己的需求购买 ARM 的核心授权,并在此基础上进行定制化设计。这种模式降低了硬件开发的门槛和成本,使得更多的厂商能够进入市场,推出各种不同价位的产品。此外,ARM 架构的处理器在制程工艺上也具有一定的优势,能够在较低的成本下实现较高的集成度,进一步降低了硬件成本。这使得 ARM 架构在嵌入式设备、物联网设备以及一些低成本的服务器应用中具有明显的价格优势。X86:成本与性能的平衡X86 架构的处理器由于研发成本高、硬件设计复杂,通常价格较高。尤其是在高端服务器市场,X86 处理器的价格相对昂贵。然而,X86 架构在性能方面的优势使得它在一些对性能要求极高的应用场景中仍然具有不可替代的地位。在这些场景下,用户愿意为高性能的计算能力支付较高的成本。同时,随着技术的不断进步,X86 处理器厂商也在努力降低成本,通过优化生产工艺和提高产品的集成度,使得 X86 处理器在价格上逐渐具有竞争力。兼容性与生态系统X86:成熟的生态系统X86 架构在桌面和服务器领域拥有悠久的历史,经过多年的发展,已经形成了非常成熟的生态系统。几乎所有的主流操作系统,如 Windows、Linux 等,都对 X86 架构提供了全面的支持。此外,大量的软件应用程序也都是基于 X86 架构进行开发的,这使得 X86 架构在兼容性方面具有无可比拟的优势。无论是办公软件、游戏还是专业的设计和开发工具,都能够在 X86 架构的 Linux 主机上稳定运行。ARM:快速发展的生态虽然 ARM 架构在生态系统方面相对 X86 架构起步较晚,但近年来取得了飞速的发展。随着 ARM 架构在移动设备和嵌入式系统中的广泛应用,越来越多的软件开发者开始关注 ARM 平台。Linux 作为一款开源的操作系统,对 ARM 架构的支持也越来越完善。许多主流的 Linux 发行版,如 Ubuntu、Debian 等,都已经能够在 ARM 架构的设备上稳定运行。此外,一些云服务提供商也开始推出基于 ARM 架构的云服务器,为开发者提供了更多的选择。然而,与 X86 架构相比,ARM 架构的生态系统仍然相对薄弱,尤其是在一些专业软件和企业级应用方面,还需要进一步的完善和发展。应用场景ARM 架构的应用领域ARM 架构凭借其低功耗、低成本和灵活的设计,在移动设备、物联网、嵌入式系统以及边缘计算等领域占据了主导地位。在移动设备市场,几乎所有的智能手机和平板电脑都采用了 ARM 架构的处理器。在物联网领域,ARM 架构的芯片被广泛应用于各种智能设备,如智能家居设备、智能穿戴设备等。在嵌入式系统中,ARM 架构的处理器由于其体积小、功耗低、性能稳定等特点,成为了许多工业控制、医疗设备、汽车电子等领域的首选。此外,随着边缘计算的兴起,ARM 架构的设备因其能够在靠近数据源的地方进行实时数据处理,也得到了越来越广泛的应用。X86 架构的应用场景X86 架构则在传统的桌面电脑、服务器以及高性能计算等领域发挥着重要作用。在桌面电脑市场,X86 架构凭借其强大的性能和良好的兼容性,满足了用户对于办公、娱乐、游戏等各种应用的需求。在服务器领域,X86 架构的服务器占据了主导地位,为企业提供了强大的计算、存储和网络服务。在高性能计算领域,X86 架构的超级计算机能够处理复杂的科学计算、天气预报、金融分析等任务,推动了科学研究和技术创新的发展。总结ARM 架构和 X86 架构在 Linux 主机领域各有千秋。ARM 架构以其低功耗、低成本和灵活的设计,在移动设备、物联网和嵌入式系统等领域展现出了强大的竞争力;而 X86 架构则凭借其高性能、成熟的生态系统和广泛的兼容性,在桌面电脑、服务器和高性能计算等领域占据着重要地位。随着技术的不断发展,两者之间的界限也在逐渐模糊,ARM 架构在性能方面不断提升,开始向服务器和高性能计算领域进军;而 X86 架构也在努力降低功耗和成本,以适应更多的应用场景。在未来的发展中,我们有理由相信,ARM 架构和 X86 架构将继续相互竞争、相互促进,为 Linux 主机的发展带来更多的创新和机遇。希望通过本文的介绍,能够帮助你更好地理解 Linux 主机在 ARM 架构和 X86 架构之间的区别,从而在选择和使用 Linux 主机时做出更加明智的决策。如果你对这两种架构还有其他的疑问或者想法,欢迎在评论区留言讨论。
-
我目前手上有一个棘手的需求,需要在服务器上利用KVM运行32位的Linux操作系统。通过搜索网络我发现许多64位的ARM处理器已经不具备32位的支持,或者仅仅在个别EL下支持32位。我正在寻找具备EL1和EL0下的32位支持、且具备虚拟化扩展的64位ARM处理器。请问有使用过搭载鲲鹏916和鲲鹏920处理器的服务器的同行吗?鲲鹏916和鲲鹏920是否具备EL0和EL1下的32位支持?
-
安装安装前,检查host系统的/opt/exagear目录是否存在。如存在,用户可根据实际情况,选择在当前版本上进行升级,参见“升级ExaGear for Server on Ubuntu”,也可选择卸载当前已安装版本,参见“卸载ExaGear for Server on Ubuntu”,然后重新进行新版本的安装,新版本安装参见如下。ExaGear for Server on Ubuntu发布件包含如下六个安装包:exagear-core-x32a64_<package_version>_arm64.debexagear-core-x64a64_<package_version>_arm64.debexagear-integration_<package_version>_all.debexagear-utils_<package_version>_all.debexagear-guest-for-ubuntu-<os_version>-x86_64_<package_version>_all.debexagear-cfg-builder-a64_<package_version>_arm.deb其中exagear-guest-for-ubuntu-<os_version>-x86_64-<package_version>_all.deb 需要通过以下链接单独获取:- Ubuntu 18.04 guest系统包- Ubuntu 20.04 guest系统包执行以下命令安装exagear: sudo dpkg -i exagear-utils_<package_version>_all.deb exagear-core-x64a64_<package_version>_arm64.deb exagear-core-x32a64_<package_version>_arm64.deb exagear-guest-for-ubuntu-<os_version>-x86_64-<package_version>_all.deb exagear-integration_<package_version>_all.deb exagear-cfg-builder-a64_<package_version>_arm.debsudo apt-get install -f 注意:上述命令中的<package_version>表示ExaGear包的版本号,例如3213。安装时需根据实际获取的安装包版本号执行操作,否则会提示失败。上述命令中的<os_version>表示guest系统的版本号,安装时需根据实际获取的安装包版本号执行操作,否则会提示失败。此处<os_version>以for Ubuntu 20.04为例,则<os_version>为2004。运行guest系统运行exagear命令,即可启动一个x86 shell,也称为guest shell,进入到虚拟的x86 OS环境。exagear显示:Starting the shell in the guest image /opt/exagear/images/ubuntu-<os_version>-x86_64arch显示:x86_64至此,你已置身于x86运行环境,根目录在host系统上的绝对路径为:/opt/exagear/images/ubuntu-<os_version>-x86_64。在这里,guest shell的运行情况和在x86机器上一样。须知用户账号在host系统和guest系统之间是共享的。在guest系统中增加或删除用户时,host上也会自动进行相应的修改。/home目录也在host系统和guest系统之间共享(更多host和guest系统之间共享目录的详细信息,请参考配置ExaGear for Server on Ubuntu)。运行exit命令即可退出guest会话。exit至此,你又回到了host系统。执行如下命令:arch显示:aarch64。安装x86应用程序x86或x86_64应用程序以及依赖库的安装等,需要在guest环境中进行。推荐使用apt工具安装x86应用,首先要配置apt源,需要进入guest环境后进行配置,相关操作和在x86机器上的操作一样。如果你需要在本地网络中通过/etc/environment使用代理配置,确保你已经通过如下命令将配置复制到了guest系统(更多请参考“配置ExaGear for Server on Ubuntu”)。sudo cp /etc/environment /opt/exagear/images/ubuntu-<os_version>-x86_64/etc/environment运行exagear命令进入guest环境,以安装x86的nginx为例:xagear示:Starting the shell in the guest image /opt/exagear/images/ubuntu-<os_version>-x86_64通过apt-get进行安装:sudo apt-get updatesudo apt-get install nginx在guest环境中安装x86应用程序,guest 环境中查看到的路径如果是/path/to/binary,则实际路径是:/opt/exagear/images/ubuntu-<os_version>-x86_64/path/to/binary。比如,在guest环境中 nignx的路径为 /usr/sbin/nginx,在host 环境上的实际路径为/opt/exagear/images/ubuntu-<os_version>-x86_64/usr/sbin/nginx。如果x86应用是以deb安装包方式提供的,需要将该安装包拷贝至guest系统可见的目录,推荐个人home目录“~/”,然后进入guest环境,通过apt-get进行安装,以安装x86 xxx.deb包为例:exagear显示:Starting the shell in the guest image /opt/exagear/images/ubuntu-<os_version>-x86_64通过apt-get进行安装:sudo apt-get install xxx运行x86应用程序x86应用程序安装完成后,在guest和host中均可运行。你可以运行exagear命令启动guest shell,并运行任何x86应用程序,运行方式和在x86系统中一样。例如:exagear显示:Starting the shell in the guest image /opt/exagear/images/ubuntu-<os_version>-x86_64 which nginx显示:/usr/sbin/nginx/usr/sbin/nginx -h在host会话中,你有如下两种选择:在同一行中输入exagear -- 命令和guest系统内部的x86应用程序路径。例如:exagear -- /usr/sbin/nginx -h输入x86应用程序二进制文件的完整路径。这个文件位于一个特定的目录下:/opt/exagear/images/ubuntu-<os_version>-x86_64,即x86运行环境。例如: /opt/exagear/images/ubuntu-<os_version>-x86_64/usr/sbin/nginx -h默认配置下,x86应用程序只能访问x86运行环境中的文件,如果需要访问host系统上的某些文件,可以通过设置host与guest共享目录文件方式。详细配置方法请参考“配置ExaGear for Server on Ubuntu”中的host系统和guest系统共享的共享章节。注:对于脚本程序,推荐进入guest环境后运行,或者在host上运行该命令:exagear -- /path/to/script以test.sh脚本为例,脚本内容:#!/bin/bashif [ `arch` == 'x86_64' ]then echo "ok"else echo "fail"fi运行结果对比:./test.sh显示:failexagear -- ./test.sh显示:ok配置ExaGear for Server on Ubuntuhost系统和guest系统共享的目录ExaGear for Server整个文件系统对host系统应用程序可见,只有guest系统文件对Linux on x86应用程序可见。图1 host系统和guest系统的文件系统(以Ubuntu 18为例)放大配置文件/opt/exagear/images/ubuntu-<os_version>-x86_64/.exagear/vpaths-list包含了host系统和guest系统共享的一系列目录和文件。cat /opt/exagear/images/ubuntu-<os_version>-x86_64/.exagear/vpaths-list显示:/home//etc/adduser.conf/etc/deluser.conf...以下文件及配置会在host系统和guest系统中共享使用:User accountsuser groupsuser privileges/home directorieshost configurationssystem information provided by Linux kerneldevices and diskssocketspidfilesmount pointslogstemporary files如需创建一个在host系统和guest系统之间共享的目录或文件,可以选择如下两种方法:方法1:修改vpaths-list配置文件。修改vpaths-list配置文件的操作步骤如下:确保host系统中存在所需的目录(或文件)。在guest系统中创建同名的假目录(或文件)。将目录(或文件)的完整路径另取一行添加到配置文件opt/exagear/images/ubuntu-<os_version>-x86_64/.exagear/vpaths-list中。请注意,配置文件的目录应以“/”结尾方法2:将host系统要共享的目录挂载到guest系统以/newdir为例,使用coreutils中的mount实用程序将host系统要共享的目录挂载到guest系统:在x86运行环境中创建挂载点:sudo mkdir /opt/exagear/images/ubuntu-<os_version>-x86_64/shareddir将newdir挂载到shareddir挂载点下:sudo mount --bind /newdir /opt/exagear/images/ubuntu-<os_version>-x86_64/shareddir说明在卸载ExaGear for Server前,需要卸载挂载的目录:sudo umount /opt/exagear/images/ubuntu-<os_version>-x86_64/shareddir代理配置/etc/environment在host系统和guest系统之间不共享。因此,如果你需要在本地网络中通过/etc/environment使用代理配置,确保你已经使用如下命令将配置复制到guest系统:sudo cp /etc/environment /opt/exagear/images/ubuntu-<os_version>-x86_64/etc/environmentsysctl和网络配置guest系统可以访问/proc/sys/中的sysctl设置,但不能修改/etc/sysctl.conf或/etc/sysctl.d/。sysctl参数的配置应在host系统上进行。guest系统可以访问描述网络配置的文件(例如/etc/resolv.conf)但guest系统不可修改网络设置。网络设置的配置应在host系统上进行。guest SysV风格的init脚本和systemd在host中的集成Ubuntu采用systemd作为init系统,并支持SysV风格的init脚本作为legacy。许多Ubuntu包中会提供这些服务的配置。默认情况下,系统采用全自动集成机制将guest服务表示为host服务。guest服务被注册为host服务,服务名称相同。因此,你可以从guest和host shell来管理guest服务(在这种情况下,host服务会将所有start/stop/testconfig等请求委托给各自的guest服务)。以Ubuntu为例: exagear显示:Starting the shell in the guest image /opt/exagear/images/ubuntu-<os_version>-x86_64 sudo apt-get updatesudo apt-get install nginxexitsudo service nginx startservice nginx status显示:● nginx.service - nginx - high performance web serverLoaded: loaded (/run/systemd/generator.late/nginx.service; generated)Active: active (running) since Fri 2019-05-17 11:56:35 EDT; 2h 54min ago ...你可以通过列出guest系统的/opt/exagear/images/ubuntu-<os_version>-x86_64/etc/init.d/和/opt/exagear/images/ubuntu-<os_version>-x86_64/lib/systemd/system/目录中的文件来查看guest系统中注册了哪些服务。内存序模型ExaGear for Server 默认为Linux on x86应用程序提供弱内存序模型。如果软件依赖于x86强内存序模型,则可能需要强制ExaGear严格遵循x86强内存序模型。可以通过修改ExaGear for Server配置文件来完成: sudo sed -i -e '/EXAGEAR_SMO_MODE="/s/fbase"/all"/' /etc/exagear-x86_32.confsudo sed -i -e '/EXAGEAR_SMO_MODE="/s/fbase"/all"/' /etc/exagear-x86_64.conf使用以下命令恢复弱内存序模型: sudo sed -i -e '/EXAGEAR_SMO_MODE="/s/all"/fbase"/' /etc/exagear-x86_32.confsudo sed -i -e '/EXAGEAR_SMO_MODE="/s/all"/fbase"/' /etc/exagear-x86_64.conf动态二次优化ExaGear 动态二次优化可以在程序翻译运行时动态识别优化的机会,进一步提升翻译效果。动态二次优化依赖硬件SPE功能(SPE是否可用的检测手段查看6-FAQ),以系统服务进程的形式存在。启用动态二次优化需要设置ExaGear配置选项EXAGEAR_USE_OPT="y"。该选项默认为"n"关闭。例如:分别在32位和64位应用程序的配置文件/etc/exagear-x86_32.conf 和 /etc/exagear-x86_64.conf中,设置EXAGEAR_USE_OPT="y",以开启动态二次优化。启动动态二次优化服务: sudo systemctl start exagear-x86_64-opt.servicesudo systemctl start exagear-x86_32-opt.service查看动态二次优化服务状态: sudo systemctl status exagear-x86_64-opt.servicesudo systemctl status exagear-x86_32-opt.service此时可以看到opt.x64a64.socket、opt.x32a64.socket 的进程服务启动。设置/关闭动态二次优化服务自启动: sudo systemctl enable exagear-x86_64-opt.servicesudo systemctl disable exagear-x86_64-opt.servicesudo systemctl enable exagear-x86_32-opt.servicesudo systemctl disable exagear-x86_32-opt.service须知设置EXAGEAR_USE_OPT="y"之前,请确认exagear-x86_*-opt.service相关进程处于开启状态。返回地址优化ExaGear for Server默认关闭了返回地址优化,相关配置及其值设置为EXAGEAR_DISABLE_NATIVE_RETURN_ADDR_OPT="y",如需开启返回地址优化,参考以下操作实现。sudo sed -i -e '/EXAGEAR_DISABLE_NATIVE_RETURN_ADDR_OPT="/s/y"/n"/' /etc/exagear-x86_32.confsudo sed -i -e '/EXAGEAR_DISABLE_NATIVE_RETURN_ADDR_OPT="/s/y"/n"/' /etc/exagear-x86_64.confx86位相同精度在ExaGear中,如果需要以与x86 bit位相同精度进行32/64位的浮点计算(例如,对于使用 SIMD 寄存器的计算),应该启用额外的 ExaGear 选项EXAGEAR_X86_PRECISE_FP。此选项默认为禁用,但可以通过将 EXAGEAR_X86_PRECISE_FP 参数设置为 "y"进行开启,例如:在分别针对32位和64位应用程序进行符号链接配置的文件/etc/exagear- x86_32.conf 和 /etc/exagear-x86_64.conf中添加EXAGEAR_X86_PRECISE_FP="y"。说明启用EXAGEAR_X86_PRECISE_FP选项可能会显著降低ExaGear的整体性能。80位 x86精度在 ExaGear 中,如果需要具有 80 位 x86 精度,则应启用额外的 ExaGear 选项 EXAGEAR_X86_PRECISE_80BIT_FP。此选项默认为禁用,但可以通过将EXAGEAR_X86_PRECISE_80BIT_FP参数设置为“y”进行开启,例如:在分别针对32位和64位应用程序进行符号链接配置的文件/etc/exagear- x86_32.conf 和 /etc/exagear-x86_64.conf中添加EXAGEAR_X86_PRECISE_80BIT_FP="y"。
-
📢前言在 Linux 系统的广袤世界里,高效的程序调试与对进程概念的深刻理解,宛如鸟之双翼、车两轮,是开发者不可或缺的重要技能。GDB(GNU Debugger)作为一款强大的调试工具,犹如开发者手中的精密仪器,能够深入程序内部,精准定位问题所在。而进程概念则是理解 Linux 操作系统运行机制的核心,它关乎资源分配、任务调度等诸多关键环节。本文将引领大家深入探索 GDB 调试工具与进程概念,通过丰富的实例和详细的解析,助力大家掌握这两项重要技能。 🏳️🌈预备程序的发布⽅式有两种, debug 模式和 release 模式, Linux gcc/g++ 出来的⼆进制程序,默认是 release 模式。 要使⽤gdb调试,必须在源代码⽣成⼆进制程序的时候, 加上 -g 选项,如果没有添加,程序⽆法被编译 $ gcc mycmd.c -o mycmd # 默认模式,不⽀持调试$ file mycmdmycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamicallylinked, interpreter /lib64/ld-linux-x86-64.so.2,BuildID[sha1]=82f5cbaada10a9987d9f325384861a88d278b160, for GNU/Linux3.2.0, not stripped$ gcc mycmd.c -o mycmd -g # debug模式$ file mycmdmycmd: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamicallylinked, interpreter /lib64/ld-linux-x86-64.so.2,BuildID[sha1]=3d5a2317809ef86c7827e9199cfefa622e3c187f, for GNU/Linux3.2.0, with debug_info, not stripped 🏳️🌈常见使用开始: gdb File退出: ctrl + d 或 quit 调试命令 这是要用到的两个文件,执行生成 myexe 文件,然后用 gdb 调试 [wzy@VM-20-5-centos lesson11]$ cat code.c#include <stdio.h> int Sum(int s, int e){ int sum = 0; int i = s; for(; i <= e; ++i){ sum += i; } return sum;} int main(){ printf("process is running\n"); int start = 1; int end = 100; int result = Sum(start, end); printf("process has done, result: %d\n", result);}[wzy@VM-20-5-centos lesson11]$ cat Makefilemyexe:code.cgcc -o $@ $^ -g .PHONY:cleanclean:rm -f myexe[wzy@VM-20-5-centos lesson11]$ lltotal 20-rw-rw-r-- 1 wzy wzy 324 Jan 17 15:54 code.c-rw-rw-r-- 1 wzy wzy 64 Jan 17 16:02 Makefile-rwxrwxr-x 1 wzy wzy 9688 Jan 17 16:02 myexe l + 数字:查看当前文件中以想要查找的行为中间行的上下10行 l + 字符:效果同上 回车:gdb会记录最近的一条命令,直接回车会继续执行 b + 数字:在指定行添加断点,若当前行没有数据,会提示info b:显示当前有哪些断点 d + 数字:删除指定序号的断点 🏳️🌈常见技巧 - cgdb先需要安装一下 cgdb - sudo tum install -y cgdb cgdb的用法和gdb如出一辙,但是多了一个可视化界面,能够在调试的同时观察代码 cgdb myexe1 👥总结本篇博文对 gdb_进程概念 做了一个较为详细的介绍,不知道对你有没有帮助呢 觉得博主写得还不错的三连支持下吧!会继续努力的~———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_77954967/article/details/145208356
-
SD卡烧录好欧拉系统,HiLens能够设置SD卡为启动首选项,使用SD卡烧录的系统吗?备注:HiLens根目录只有2GB,空间太少了,安装几个库就没有空间了,总计32GB的空间,分成了很多个区,不敢贸然合并,所以想搞个大容量SD卡,烧录系统使用
-
grep 是 Unix 和 Linux 系统中广泛使用的文本搜索工具,它允许用户搜索包含指定模式的文本行。以下是 grep 命令的基本用法及示例:一、基本用法1grep [选项] 模式 [文件...]二、常用选项-i:忽略大小写。-v:反向选择,显示不包含模式的行。-n:显示匹配行的行号。-l:显示包含匹配模式的文件名。-L:显示不包含匹配模式的文件名。-c:只输出匹配到的行数。-w:只匹配整个单词。-x:只匹配整行。-r 或 -R:递归搜索目录中的文件。-A NUM:匹配行及后面 NUM 行一起显示。-B NUM:匹配行及前面 NUM 行一起显示。-C NUM:匹配行及前后各 NUM 行一起显示。三、正则表达式grep 支持正则表达式,这使得它可以进行复杂的文本搜索。例如:.:匹配任意单个字符。*:匹配前一个字符零次或多次。^:匹配行的开始。$:匹配行的结束。[]:匹配括号内的任意一个字符。|:表示或的关系,如 a|b 匹配 a 或 b。():分组,用于后向引用。四、示例搜索文本假设有一个文件 example.txt,内容如下:Hello World hello unix GREP is powerful要在文件中搜索包含 "hello" 的行(忽略大小写),可以使用以下命令:1grep -i "hello" example.txt输出:Hello World hello unix使用正则表达式要在文件中搜索以 "G" 开头并以 "p" 结尾的单词,可以使用以下命令:1grep -w "\bG\w*p\b" example.txt输出:GREP is powerful显示行号要在文件中搜索包含 "World" 的行并显示行号,可以使用以下命令:1grep -n "World" example.txt输出:1:Hello World反向选择要在文件中搜索不包含 "unix" 的行,可以使用以下命令:1grep -v "unix" example.txt输出:Hello World GREP is powerful递归搜索要在目录 mydir 及其子目录中的所有文件中搜索包含 "error" 的行,可以使用以下命令:1grep -r "error" mydir/
-
LKVS 介绍 Linux内核验证套件(LKVS)是英特尔内核组开发的一款面向Linux内核测试的综合测试工具集。目前已在openEuler社区开源。它汇聚了英特尔内核开发和验证团队多年积累的专业知识,具有专业性轻量级、低耦合、高覆盖三大特点,可广泛应用于Linux系统开发和验证的多个场景。 高覆盖测试内容 1. 全面测试范围 LKVS集成了600余项测试用例,覆盖Linux内核的20余项关键特性,测试领域涉及CPU特性、电源管理和安全特性等方面。具体来说包括这些英特尔平台的功能: CET(Control flow Enhancement Technology),cstate,Intel_TH(Trace Hub),Intel_PT,UMIP(User-Mode Instruction Prevention),xsave,IFS(In Field Scan),FRED (in progress),guest-test,IFS,ISST,PMU,RAPL,SDSI,splitlock,tdx-compliance,thermal,CPU topology,UFS,AMX。 2. 深入测试覆盖 测试内容高度专业,紧跟英特尔当下开发中的新平台、新功能,能够有效覆盖到关键内核路径和硬件集成细节,挖掘异常并提高稳定性。 3. 持续扩展测试 LKVS套件从上线开始,就不断在迭代更新中。通过涵盖广泛的专业测试内容,LKVS能够有效保证Linux环境在英特尔平台上的健壮性,发现新的或者留存的隐患和异常。 灵活解耦的测试框架 1. 解耦组件设计 LKVS是从大量内部功能测试、回滚测试框架中解耦并独立出来的测试用例。最大程度降低了测试用例的耦合度,使得测试用例更易于移植和扩展。可以方便地集成至各类基础设施比如openEuler社区的EulerPipeLine(CI/CD)系统。 2. 多语言测试脚本 测试套件支持C语言、bash、python等语言编写测试用例。因为各个功能测试之间相互独立,使得各个不同的功能测试之间零耦合,使其对开发者更友好,易于贡献测试用例。 通过这种可扩展可定制的低耦合架构,有利于LKVS的长期演进。 丰富的场景应用 LKVS可广泛服务于以下关键场景: 1. CI/CD集成检验 自动化测试减少集成风险,提高质量管控水平; 2. 基线功能验证 充分覆盖基线功能,成熟度评估; 3. 硬件兼容性 验证最新硬件特性,避免兼容性问题; 4. 安全审计 主动发掘和减轻安全隐患; 5. 回归测试 定位修补程序和版本升级可能引入的问题。 后续规划 本项目已开源在openEuler社区,并且已经集成到EulerPipeLine系统中,计划中的开发: ● 支持将要发布的新平台的新功能。 ● 支持新的内核特性。 ● 进一步完善框架本身,提高测试用例的可扩展性和可移植性。 ● 完善虚拟机测试场景,多虚拟机测试场景。 LKVS是结合Linux Kernel专业知识而产出的工程化测试集,可广泛应用于Linux系统开发和验证的多个场景。其专业性、低耦合和高覆盖等特点为生态中的众多参与方创造了价值。
-
前言 前一个系列【Linux之权限】我们详细地了解了Linux中权限的问题,那么,我们理解了用户间的“隔离”,那么又该怎么进行多用户协作呢? 本文就来回答这个问题。 现象 在正式回答这个问题之前,我们先来看一下这个现象。 我们在用户whb的所属的一个目录里以root身份创造了一个文件root.txt,其拥有者和所属组都是root,然后我们通过sudo chmod a-rwx root.txt把所有人的所有权限去掉。 可以看到,所以现在我们的whb没法cat和echo这个root.txt,没有读写权限。但是我们居然可以把这个文件删除: 这个问题的原因是什么?这正常吗?这其实是正常的。因为当前我们是在lesson5目录下: whb是这个目录的拥有者,对该目录有写权限,而对目录有写权限就意味着可以在这个目录里创建和删除文件。因为一个文件能否被删除和文件自己无关!而是和所处的目录权限有关。 一个用户对文件所处的目录有写权限,那么就算这个文件没有给这个用户开放任何权限,这个用户却可以把这个文件删除。 不过除了root,一般情况下没有人可以进入“我家”,在“我家”新建文件。 多用户协作 但若两人想协作呢?想共同修改或者查看一个配置文件。 它俩账号之间隔离,进入不了彼此的目录。如果两个用户之间要进行文件级别的协作呢?那么这个文件就不能放在任何一个用户的私人账号里。 应该放在哪? Linux里不只有home目录。 Linux的根目录下有一个和home平级的路径:tmp ll / 我们可以看到根目录里: bin或者user/bin里面一般放的就是系统的指令; boot里面一般是与启动相关的操作系统或配置文件; dev是启动时操作系统所识别到的各种设备,如键盘、显示器、网卡; etc都是系统启动后的一些配置文件; home我们已经知道了; lib或者user/lib代表的一般是一些动静态库; lib64指的是64位的库; lost+found这个不管; media是一些媒体设备(u盘); mnt和opt不管; proc是查看进程相关的信息; root就是root账号; var是日志信息; …… 我们将目光放到tmp路径,这里面放的是系统产生的各种临时文件。 根目录可以用d选项进行查看其权限: ls -ld / 根目录的拥有者和所属组都是root,想在根目录下新建文件,也必须使用root权限。 我们现在在根目录下新建一个temp-backup目录,并把权限放开。 (共享文件夹一般都属于root) (其实以后用tmp就可以) 但是,这个目录里面的文件现在,谁都能删除。 共享类目录下,别人为什么能删自己的文件呢?因为删一个文件和文件本身无关,而是和所处目录的写权限相关。 但是other的w我们不能去掉,因为这样,其他人就不能在这个目录下创建文件,共享的作用就不复存在了。 这就形成了一个悖论。 所以 Linux中引入了一个新的权限标志位,粘滞位:t 可以看到,x的位置变成了t。 这种给目录添加的t,叫做粘滞位,任何一个人可以在共享目录下新建,但是不能让非拥有者删除。只给需要共享的目录添加粘滞位。 但这粘滞位,对root没办法,它还是可以删除里面的文件。 我们可以看回根目录下的tmp,已经有粘滞位了。所以用它就可以 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_82135086/article/details/143807906
-
一:🔥 线程互斥 🦋 1-1 进程线程间的互斥相关背景概念 临界资源:多线程执⾏流共享的资源就叫做临界资源。 临界区:每个线程内部,访问临界资源的代码,就叫做临界区。 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起保护作⽤。 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。 🦋 1-2 互斥量mutex ⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程⽆法获得这种变量。 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。 多个线程并发的操作共享变量,会带来⼀些问题。 // 操作共享变量会有问题的售票系统代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> int ticket = 100; void *route(void *arg) { char *id = (char*)arg; while ( 1 ) { if ( ticket > 0 ) { usleep(1000); printf("%s sells ticket:%d\n", id, ticket); ticket--; } else { break; } } } int main( void ) { pthread_t t1, t2, t3, t4; pthread_create(&t1, NULL, route, "thread 1"); pthread_create(&t2, NULL, route, "thread 2"); pthread_create(&t3, NULL, route, "thread 3"); pthread_create(&t4, NULL, route, "thread 4"); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); } 🥗 ⼀次执⾏结果: thread 4 sells ticket:100 ... thread 4 sells ticket:1 thread 2 sells ticket:0 thread 1 sells ticket:-1 thread 3 sells ticket:-2 为什么可能⽆法获得争取结果? if 语句判断条件为真以后,代码可以并发的切换到其他线程 usleep 这个模拟漫⻓业务的过程,在这个漫⻓的业务过程中,可能有很多个线程会进⼊该代码段 –ticket 操作本⾝就不是⼀个原⼦操作 操作并不是原⼦操作,⽽是对应三条汇编指令: load :将共享变量 ticket 从内存加载到寄存器中 update : 更新寄存器⾥⾯的值,执⾏ -1 操作 store :将新值,从寄存器写回共享变量 ticket 的内存地址 要解决以上问题,需要做到三点: 代码必须要有互斥⾏为:当代码进⼊临界区执⾏时,不允许其他线程进⼊该临界区。 如果多个线程同时要求执⾏临界区的代码,并且临界区没有线程在执⾏,那么只能允许⼀个线程进⼊该临界区。 如果线程不在临界区中执⾏,那么该线程不能阻⽌其他线程进⼊临界区。要做到这三点,本质上就是需要⼀把锁。Linux上提供的这把锁叫互斥量。 🦋 互斥量的接⼝ 🍡 初始化互斥量 初始化互斥量有两种⽅法: ⽅法1,静态分配: pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ⽅法2,动态分配: int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参数: mutex:要初始化的互斥量 attr:NULL 🍡 销毁互斥量 销毁互斥量需要注意: 使⽤ PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁 不要销毁⼀个已经加锁的互斥量 已经销毁的互斥量,要确保后⾯不会有线程再尝试加锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); 1 🍡 互斥量加锁和解锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); 调⽤ pthread_ lock 时,可能会遇到以下情况: 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功 发起函数调⽤时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么 pthread_ lock 调⽤会陷⼊阻塞(执⾏流被挂起),等待互斥量解锁。 🍧 改进上⾯的售票系统: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include <sched.h> int ticket = 100; pthread_mutex_t mutex; void *route(void *arg) { char *id = (char *)arg; while (1) { pthread_mutex_lock(&mutex); if (ticket > 0) { usleep(1000); printf("%s sells ticket:%d\n", id, ticket); ticket--; pthread_mutex_unlock(&mutex); // sched_yield(); 放弃CPU } else { pthread_mutex_unlock(&mutex); break; } } } int main() { pthread_t t1, t2, t3, t4; pthread_mutex_init(&mutex, NULL); pthread_create(&t1, NULL, route, (void*)"thread 1"); pthread_create(&t2, NULL, route, (void*)"thread 2"); pthread_create(&t3, NULL, route, (void*)"thread 3"); pthread_create(&t4, NULL, route, (void*)"thread 4"); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); pthread_mutex_destroy(&mutex); return 0; } 🦋 1-3 互斥量实现原理探究 经过上⾯的例⼦,⼤家已经意识到单纯的 i++ 或者 ++i 都不是原⼦的,有可能会有数据⼀致性问题 为了实现互斥锁操作, ⼤多数体系结构都提供了 swap 或 exchange 指令, 该指令的作⽤是把寄存器和内存单元的数据相交换, 由于只有⼀条指令, 保证了原⼦性, 即使是多处理器平台, 访问内存的总线周期也有先后, ⼀个处理器上的交换指令执⾏时另⼀个处理器的交换指令只能等待总线周期。 现在我们把 lock 和 unlock 的伪代码改⼀下 。 🦋 1-4 互斥量的封装 Mutex.hpp #pragma once #include <iostream> #include <pthread.h> namespace MutexModule { class Mutex { public: Mutex(const Mutex&) = delete; const Mutex& operator = (const Mutex&) = delete; Mutex() { int n = ::pthread_mutex_init(&_lock, nullptr); (void)n; } void Lock() { int n = ::pthread_mutex_lock(&_lock); (void)n; } void Unlock() { int n = ::pthread_mutex_unlock(&_lock); (void)n; } ~Mutex() { int n = ::pthread_mutex_destroy(&_lock); (void)n; } private: pthread_mutex_t _lock; }; class LockGuard { public: LockGuard(Mutex &mtx) :_mtx(mtx) { _mtx.Lock(); } ~LockGuard() { _mtx.Unlock(); } private: Mutex &_mtx; }; } 🍧 抢票的代码就可以更新成为 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> #include "Mutex.hpp" using namespace MutexModule; int ticket = 1000; Mutex mutex; void *route(void *arg) { char *id = (char *)arg; while (1) { LockGuard lockguard(mutex); // 使⽤RAII⻛格的锁 if (ticket > 0) { usleep(1000); printf("%s sells ticket:%d\n", id, ticket); ticket--; } else { break; } } return nullptr; } int main() { pthread_t t1, t2, t3, t4; pthread_create(&t1, NULL, route, (void *)"thread 1"); pthread_create(&t2, NULL, route, (void *)"thread 2"); pthread_create(&t3, NULL, route, (void *)"thread 3"); pthread_create(&t4, NULL, route, (void *)"thread 4"); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); pthread_join(t4, NULL); return 0; } RAII⻛格的互斥锁, C++11也有,⽐如: std::mutex mtx; std::lock_guard<std::mutex> guard(mtx); 此处我们仅做封装,⽅便后续使⽤,详情⻅C++博客 二:🔥 线程同步 🦋 2-1 条件变量 🍥 当⼀个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。 🍥 例如⼀个线程访问队列时,发现队列为空,它只能等待,只到其它线程将⼀个节点添加到队列中。这种情况就需要⽤到条件变量。 🦋 2-2 同步概念与竞态条件 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从⽽有效避免饥饿问题,叫做同步。 竞态条件:因为时序问题,⽽导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解 🦋 2-3 条件变量函数 🌯 初始化 int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 参数: cond:要初始化的条件变量 attr:NULL 🌯 销毁 int pthread_cond_destroy(pthread_cond_t *cond) 1 🌯 等待条件满⾜ int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 参数: cond:要在这个条件变量上等待 mutex:互斥量,后⾯详细解释 🌯 唤醒等待 int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond); 1 2 简单案例: 我们先使⽤ PTHREAD_COND / MUTEX_INITIALIZER 进⾏测试,对其他细节暂不追究 然后将接⼝更改成为使⽤ pthread_cond_init / pthread_cond_destroy 的⽅式,⽅便后续进⾏封装 #include <iostream> #include <string> #include <pthread.h> #include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; void *active(void *args) { std::string name = static_cast<const char*>(args); while(true) { pthread_mutex_lock(&mutex); // 没有对资源是否就绪的判定 pthread_cond_wait(&cond, &mutex); printf("%s is active!\n", name.c_str()); pthread_mutex_unlock(&mutex); } return nullptr; } int main() { pthread_t tid1, tid2, tid3; pthread_create(&tid1, nullptr, active, (void*)"thread-1"); pthread_create(&tid1, nullptr, active, (void*)"thread-2"); pthread_create(&tid1, nullptr, active, (void*)"thread-3"); sleep(1); printf("Main thread ctrl begin...\n"); while(true) { printf("main wakeup thread...\n"); pthread_cond_signal(&cond); sleep(1); } pthread_join(tid1, nullptr); pthread_join(tid2, nullptr); pthread_join(tid3, nullptr); return 0; } 🧋运行结果: Main thread ctrl begin... main wakeup thread... thread-1 is active! main wakeup thread... thread-2 is active! main wakeup thread... thread-3 is active! 三:🔥 ⽣产者消费者模型 321原则(便于记忆) 三种关系 两个角色 一个消费场所(某种数据结构组织的连续的内存空间) 🥙 生产者-消费者模型(Producer-Consumer Model)是一种经典的多线程同步问题,它描述了两个线程(或进程)之间的协作:一个或多个生产者线程生成数据项,并将它们放入缓冲区中;一个或多个消费者线程从缓冲区中取出数据项,并进行处理。这个模型通常用于解决生产者和消费者在不同速度下工作时的同步和数据传输问题。 🦋 3-1 为何要使⽤⽣产者消费者模型 💜 ⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取,阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒。这个阻塞队列就是⽤来给⽣产者和消费者解耦的。 🦋 3-2 ⽣产者消费者模型优点 🌶️ 解耦 🌶️ 支持并发 🌶️ 支持忙闲不均 🦋 3-3 基于BlockingQueue的⽣产者消费者模型 🍱 3-3-1 BlockingQueue 在多线程编程中阻塞队列 (Blocking Queue) 是⼀种常⽤于实现⽣产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放⼊了元素;当队列满时,往队列⾥存放元素的操作也会被阻塞,直到有元素被从队列中取出 (以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞) 🍱 3-3-2 C++ queue模拟阻塞队列的⽣产消费模型 代码: 为了便于理解,我们以单⽣产者,单消费者,来进⾏讲解。 刚开始写,我们采⽤原始接⼝。 我们先写单⽣产,单消费。然后改成多⽣产,多消费(这⾥代码其实不变,这里用到了更后面的cond的封装头文件)。 BlockQueue.hpp #pragma #include <iostream> #include <queue> #include <pthread.h> #include "Mutex.hpp" #include "Cond.hpp" namespace BlockQueueModule { using namespace LockModule; using namespace CondModule; // version2 static const int gcap = 10; template<typename T> class BlockQueue { public: BlockQueue(int cap = gcap) :_cap(cap) ,_cwait_num(0) ,_pwait_num(0) {} bool IsFull() { return _q.size() == _cap; } bool IsEmpty() { return _q.empty(); } void Equeue(const T &in) // 生产者 { LockGuard lockguard(_mutex); // 生产数据有条件 // 结论1:在临界区中等待是必然的(当前) while(IsFull()) // 为了防止伪唤醒 使用while判断 { std::cout << "生产者进入等待..." << std::endl; // 2. 等待 释放锁 _pwait_num++; _productor_cond.Wait(_mutex); // wait的时候,必定是持有锁的 _pwait_num--; // 3. 返回,线程被唤醒 重新申请并持有锁 std::cout << "生产者被唤醒..." << std::endl; } // 4. isfull不满足 || 线程被唤醒 _q.push(in); // 生产 // 肯定有数据 if(_cwait_num) { std::cout << "叫醒消费者" << std::endl; _consumer_cond.Notify(); } } void Pop(T* out) // 消费者 { LockGuard lockguard(_mutex); while(IsEmpty()) { std::cout << "消费者进入等待..." << std::endl; _cwait_num++; _consumer_cond.Wait(_mutex); // 伪唤醒 _cwait_num--; std::cout << "消费者被唤醒..." << std::endl; } // 4. 线程被唤醒 *out = _q.front(); _q.pop(); // 一定不为满 if(_pwait_num) { std::cout << "叫醒生产者" << std::endl; _productor_cond.Notify(); } } ~BlockQueue() {} private: std::queue<T> _q; // 临界资源 Mutex _mutex; // 互斥 Cond _productor_cond; // 生产者条件变量 Cond _consumer_cond; // 消费者条件变量 int _cap; // bq最大容量 int _cwait_num; int _pwait_num; }; } main.cc #include <functional> #include "BlockQueue.hpp" #include "Task.hpp" #include <unistd.h> using namespace BlockQueueModule; using namespace TaskModule; using task_t = std::function<void()>; void *Comsumer(void *args) { BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args); int data = 10; while(true) { // 1. 从bq中拿到数据 bq->Pop(&data); // 2. 做处理 printf("Comsumer 消费了一个数据:%d\n", data); data++; } } void *Productor(void *args) { BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args); // 1. 从外部获取数据 int data = 10; while(true) { sleep(2); // 2. 生产到队列中 printf("Productor 生产了一个数据:%d\n", data); bq->Equeue(data); data++; } } int main() { BlockQueue<int> *bq = new BlockQueue<int>(5); // 共享资源 -> 临界资源 // 单生产 单消费 pthread_t c1, c2, p1, p2, p3; pthread_create(&c1, nullptr, Comsumer, (void*)bq); pthread_create(&p3, nullptr, Productor, (void*)bq); pthread_join(c1, nullptr); pthread_join(p3, nullptr); delete bq; return 0; } main.cc #include "BlockQueue.hpp" #include <unistd.h> using namespace BlockQueueModule; void *Comsumer(void *args) { BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args); int data = 10; while(true) { // 1. 从bq中拿到数据 bq->Pop(&data); // 2. 做处理 printf("Comsumer 消费了一个数据:%d\n", data); data++; } } void *Productor(void *args) { BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args); // 1. 从外部获取数据 int data = 10; while(true) { sleep(2); // 2. 生产到队列中 printf("Productor 生产了一个数据:%d\n", data); bq->Equeue(data); data++; } } int main() { BlockQueue<int> *bq = new BlockQueue<int>(5); // 共享资源 -> 临界资源 // 单生产 单消费 pthread_t c1, c2, p1, p2, p3; pthread_create(&c1, nullptr, Comsumer, (void*)bq); pthread_create(&p3, nullptr, Productor, (void*)bq); pthread_join(c1, nullptr); pthread_join(p3, nullptr); delete bq; return 0; } 输出结果: root@hcss-ecs-a9ee:~/code/linux/112/lesson31/2.BlockQueue# ./bq 消费者进入等待... Productor 生产了一个数据:10 叫醒消费者 消费者被唤醒... Comsumer 消费了一个数据:10 消费者进入等待... Productor 生产了一个数据:11 叫醒消费者 消费者被唤醒... Comsumer 消费了一个数据:11 四:🔥 为什么 pthread_cond_wait 需要互斥量? 条件等待是线程间同步的⼀种⼿段,如果只有⼀个线程,条件不满⾜,⼀直等下去都不会满⾜,所以必须要有⼀个线程通过某些操作,改变共享变量,使原先不满⾜的条件变得满⾜,并且友好的通知等待在条件变量上的线程。 条件不会⽆缘⽆故的突然变得满⾜了,必然会牵扯到共享数据的变化。所以⼀定要⽤互斥锁来保护。没有互斥锁就⽆法安全的获取和修改共享数据。 按照上⾯的说法,我们设计出如下的代码:先上锁,发现条件不满⾜,解锁,然后等待在条件变量上不就⾏了,如下代码: 错误的设计 pthread_mutex_lock(&mutex); while (condition_is_false) { pthread_mutex_unlock(&mutex); //解锁之后,等待之前,条件可能已经满⾜,信号已经发出,但是该信号可能被错过 pthread_cond_wait(&cond); pthread_mutex_lock(&mutex); } pthread_mutex_unlock(&mutex); 由于解锁和等待不是原⼦操作。调⽤解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满⾜,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是⼀个原⼦操作。 (这就是为什么wait的时候需要把条件变量和锁一起传进去) int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t *mutex); 进⼊该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到 cond_ wait 返回,把条件量改成1,把互斥量恢复成原样。 🦋 4-1 条件变量使⽤规范 等待条件代码 pthread_mutex_lock(&mutex); while (条件为假) pthread_cond_wait(cond, mutex); 修改条件 pthread_mutex_unlock(&mutex); 给条件发送信号代码 pthread_mutex_lock(&mutex); 设置条件为真 pthread_cond_signal(cond); pthread_mutex_unlock(&mutex); 🦋 4-2 条件变量的封装 基于上⾯的基本认识,我们已经知道条件变量如何使⽤,虽然细节需要后⾯再来进⾏解释,但这⾥可以做⼀下基本的封装,以备后⽤. Cond.hpp #pragma once #include <iostream> #include <pthread.h> #include "Mutex.hpp" namespace CondModule { using namespace LockModule; class Cond { public: Cond() { int n = ::pthread_cond_init(&_cond, nullptr); (void)n; } void Wait(Mutex &mutex) // 让我们的线程释放曾经持有的锁! { int n = ::pthread_cond_wait(&_cond, mutex.LockPtr()); } void Notify() { int n = ::pthread_cond_signal(&_cond); (void)n; } void NotifyAll() { int n = ::pthread_cond_broadcast(&_cond); (void)n; } ~Cond() { int n = ::pthread_cond_destroy(&_cond); } private: pthread_cond_t _cond; }; } 五:🔥 POSIX信号量 POSIX 信号量和 SystemV 信号量作⽤相同,都是⽤于同步操作,达到⽆冲突的访问共享资源⽬的。但 POSIX 可以⽤于线程间同步。 🍲 初始化信号量 #include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); 参数: pshared:0表⽰线程间共享,⾮零表⽰进程间共享 value:信号量初始值 🍲 销毁信号量 int sem_destroy(sem_t *sem); 1 🍲 等待信号量 功能:等待信号量,会将信号量的值减1 为0将进行阻塞等待 int sem_wait(sem_t *sem); //P() 1 2 🍲 发布信号量 功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。 int sem_post(sem_t *sem);//V() 1 2 上⼀节⽣产者-消费者的例⼦是基于queue的,其空间可以动态分配,现在基于固定⼤⼩的环形队列重写这个程序(POSIX信号量): ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/weixin_50776420/article/details/144121359
-
1. 线程安全和重入问题🚀 1.1 基本概念 线程安全:就是多个线程在访问共享资源时,能够正确地执行,不会相互干扰或破坏彼此的执行结果。一般而言,多个线程并发同一段只有局部变量的代码时,不会出现不同的结果。但是对全局变量或者静态变量进行操作,并且没有锁保护的情况下,容易出现该问题。 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。 1.2 线程安全和重入情况 🍥 学到现在,其实我们已经能理解重入其实可以分为两种情况 多线程重入函数 信号导致一个执行流重复进入函数 ① 常见的线程不安全的情况 不保护共享变量的函数 函数状态随着被调用,状态发生变化的函数 返回指向静态变量指针的函数 调用线程不安全函数的函数 ② 常见的线程安全的情况 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的 类或者接口对于线程来说都是原子操作 多个线程之间的切换不会导致该接口的执行结果存在二义性 ③ 常见不可重入的情况 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构 可重入函数体内使用了静态的数据结构 ④ 常见可重入的情况 不使用全局变量或静态变量 不使用用malloc或者new开辟出的空间 不调用不可重入函数 不返回静态或全局数据,所有数据都有函数的调用者提供 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据 提示 :不要被上面的一系列所弄晕,其实对应概念说的都是一回事 1.3 线程安全和重入的联系区别 📌 可重入与线程安全联系 函数是可重入的,那就是线程安全的 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。 📌 可重入与线程安全区别 可重入函数是线程安全函数的一种 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。 📌 注意: 如果不考虑 信号导致一个执行流重复进入函数 这种重入情况,线程安全和重入在安全角度不做区分 但是线程安全侧重说明线程访问公共资源的安全情况,表现的是 并发线程 的特点 可重入描述的是一个函数是否能被重复进入,表示的是 函数 的特点 2. 死锁 🖊 2.1 死锁基本概念 死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 为了方便表述,假设现在线程A,线程B必须同时持有 锁1和 锁2 ,才能进行后续资源的访问 🥑 申请一把锁是原子的,但是申请两把锁就不一定了 🥑 造成的结果如下: 2.2 死锁的四个必要条件 互斥条件:一个资源每次只能被一个执行流使用 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系 2.3 避免死锁 破坏死锁的四个必要条件 破坏循环条件等待问题:资源一次性分配,使用超时机制,加锁顺序一致 #include <iostream> #include <mutex> #include <thread> #include <vector> #include <unistd.h> // 定义两个共享资源(整数变量)和两个互斥锁 int shared_resource1 = 0; int shared_resource2 = 0; std::mutex mtx1, mtx2; // ⼀个函数,同时访问两个共享资源 void access_shared_resources() { std::unique_lock<std::mutex> lock1(mtx1, std::defer_lock); std::unique_lock<std::mutex> lock2(mtx2, std::defer_lock); // 使⽤ std::lock 同时锁定两个互斥锁 std::lock(lock1, lock2); // 现在两个互斥锁都已锁定,可以安全地访问共享资源 int cnt = 10000; while (cnt--) { ++shared_resource1; ++shared_resource2; } // 当离开 access_shared_resources 的作⽤域时,lock1 和 lock2 的析构函数会被自动调⽤ // 这会导致它们各⾃的互斥量被⾃动解锁 } // 模拟多线程同时访问共享资源的场景 void simulate_concurrent_access() { std::vector<std::thread> threads; // 创建多个线程来模拟并发访问 for (int i = 0; i < 10; ++i) { threads.emplace_back(access_shared_resources); } // 等待所有线程完成 for (auto &thread : threads) { thread.join(); } // 输出共享资源的最终状态 std::cout << "Shared Resource 1: " << shared_resource1 << std::endl; std::cout << "Shared Resource 2: " << shared_resource2 << std::endl; } int main() { simulate_concurrent_access(); return 0; } 一次申请 不一次申请 避免锁未释放场景 2.4 死锁的预防 🔥 死锁的预防是通过破坏产生死锁的必要条件之一,是系统不会产生死锁。 简单方法是在系统运行之前就采取措施,即在系统设计时确定资源分配算法,消除发生死锁的任何可能性。该方法虽然比较保守、资源利用率低,但因简单明了并且安全可靠,仍被广泛采用。这是一种预先的静态策略。 🥝 破坏互斥条件 🎐 互斥条件:只有对必须互斥使用的资源的争抢才会导致死锁。 如果把只能互斥使用的资源改造为允许共享使用,则系统不会进入死锁状态。比如:SPOOLing 技术。操作系统可以采用 SPOOLing 技术 把独占设备在逻辑上改造成共享设备。比如,用SPOOLing 技术 将打印机改造为共享设备.. 该策略的缺点:并不是所有的资源都可以改造成可共享使用的资源。并且为了系统安全,很多地方还必须保护这种互斥性。因此,很多时候都无法破坏互斥条件。 🥝 破坏不可剥夺条件 💢 不剥夺条件:进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。 破坏不剥夺条件 当某个进程请求新的资源得不到满足时,它必须立即释放保持的所有资源,待以后需要时再重新申请。也就是说,即使某些资源尚未使用完,也需要主动释放,从而破坏了不可剥夺条件。 当某个进程需要的资源被其他进程所占有的时候,可以由操作系统协助,将想要的资源强行剥夺。这种方式一般需要考虑各进程的优先级(比如:剥夺调度方式,就是将处理机资源强行剥夺给优先级更高的进程使用) 该策略的缺点 实现起来比较复杂。 释放已获得的资源可能造成前一阶段工作的失效。因此这种方法一般只适用于易保存和恢复状态的资源,如CPU。 反复地申请和释放资源会增加系统开销,降低系统吞量。 若采用方案一,意味着只要暂时得不到某个资源,之前获得的那些资源就都需要放弃,以后再重新申请。如果一直发生这样的情况,就会导致进程饥饿。 🥝 破坏请求并保持条件 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己己有的资源保持不放。 可以采用静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源未满足前,不让它投入运行。一旦投入运行后,这些资源就一直归它所有,该进程就不会再请求别的任何资源了。 该策略实现起来简单,但也有明显的缺点:有些资源可能只需要用很短的时间,因此如果进程的整个运行期间都一直保持着所有资源,就会造成严重的资源浪费,资源利用率极低。另外,该策略也有可能导致某些进程饥饿。 🥝 破坏循环等待条件 循环等待条件:存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求。 可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程必须按编号递增的顺序请求资源同类资源(即编号相同的资源)一次申请完。 原理分析:一个进程只有已占有小编号的资源时,才有资格申请更大编号的资源。按此规则,已持有大编号资源的进程不可能逆向地回来申请小编号的资源,从而就不会产生循环等待的现象。 该策略的缺点: 不方便增加新的设备,因为可能需要重新分配所有的编号 进程实际使用资源的顺序可能和编号递增顺序不一致,会导致资源浪费 必须按规定次序申请资源,用户编程麻烦。 2.5 死锁的避免 避免死锁同样属于事先预防策略,并不是采取某种限制措施破坏死锁的必要条件,而是在资源动态分配过程中,防止系统进入不完全状态。 🍉 安全序列 进程可以动态的申请资源,但是系统在进行资源分配之前,必须先计算此次分配的安全性。如果计算所得是安全的,则允许分配,但如果是不安全的,则让进程等待。而所谓的安全状态就是,系统可以按照某种进程的推进顺序 这里举了一个银行给BAT三家公司借钱的例子用来引出银行家算法 这时候如果将 30亿 借给了B公司,那么手里还有 10亿元,这 10亿 已经小于3家公司最小的最多还会借的钱数,没有公司能够达到提出的最大要求,这样银行的钱就会打水漂了!!! 如果是这种情况呢? 这样按照T->B->A的顺序借钱是没有问题的,是安全的。 按照A->T->B的顺序借钱也是没有问题的。 这样我们就会得到安全序列、不安全序列和死锁的关系了 注意: (1)系统在某一时刻的安全状态可能不唯一,但这不影响对系统安全性的判断。 (2)安全状态是非死锁状态,而不安全状态并不一定是死锁状态。即系统处于安全状态一定可以避免死锁,而系统处于不安全状态则仅仅可能进入死锁状态。 原因是如果进入了不安全状态,但是没有进程去请求资源,并且有进程提前归还了一些资源,这样就不会死锁。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/island1314/article/details/144571097
-
知识铺垫 1.1 如何登入账号 首先输出ssh 用户名@公网IP ,跳出SSH用户身份验证输入密码即可。SSH(Secure Shell,安全外壳)是一种网络协议,通过加密和认证机制实现安全访问和文件传输等业务。 1.2 关于创建和删除普通账号 windows,Linux属于多用户操作系统 添加普通账号的步骤(如果创建成功就会出现@) adduser 用户名 passwd(密码) 用户名(输入密码时,是不显示密码的) passwd命令用于更改设置用户密码 删除普通账号的步骤:userdel -r 用户名@ 1.3 操作系统概念(OS) 操作系统是一款进行软硬件资源管理的软件,对于Linux是一款具体的操作系统的一个品类,而centos7 是一款具体的Linux操作系统,计算机是一个工具,被人使用的工具。 【操作系统的作用】: 提供了人机交互接口,在早期计算机使用者使用计算机的时候,面对一大堆的硬件进行操作,通过硬开关进行控制,操作系统可以避免直接操作硬件带来的麻烦,这也是操作系统最大的功能 提供了计算机软硬件资源管理 对下操作系统提供了基本的管理工作,让多种硬件处于一个稳定、高效、安全的工作环境 对上(运用软件)提供了一个稳定、高效、安全的运行环境(用户的目的) 1.4 Xshell相关快捷键 【Alt + 回车】:全屏 【Ctrl + D】:退出 【ctrl+insert】:复制(如果不行,打开Fn配合使用) 【shift+insert】:粘贴 这里不支持ctrl c和ctrl v快捷键 1.5 文件 当在电脑上进行操作时,实际上是通过操作系统来控制文件和文件夹。操作系统提供了图形用户界面(GUI)和命令行界面(CLI),让你可以方便地浏览、创建、删除、移动和修改文件和文件夹。比如我们的桌面实际上是一个文件夹,存储了在桌面上看到的所有文件和快捷方式。 【桌面属于文件夹】 当你登入windows进行如下操作: 确定你是谁 根据用户名,找到改用户名目录下的“桌面文件夹” 将桌面文件夹显示成为图形化界面 1.5.1 文件占用内存(内存≠大小) 文件虽然显示大小是0KB,但是同样会占用内存。由于【文件=文件属性+文件内容】,这里显示的时间、类型和文件名等都属于文件属性(对应的数据是字符串之类,同样占用内存),对此文件属性是数据,并且也是需要保存。 对此未来对文件的任何操作,无外乎就是对文件的属性和内容进行操作。这里操作可以通过指令控制文件,编程访问文件的内容。 1.6 路径 文件路径是用来指定文件或文件夹在计算机文件系统中的位置的,路径分为绝对路径和相对路径。 以下这些都称为路径,不同在于它们的路径分隔符 //Linux下: [root@iZ7xv21eg69v0bihv6nnufZ 111]# pwd root/111 //Windows下: D:\C—language\C++\string模拟实现 两个路径分隔符之间,一定是一个文件夹,而路径最末端,一定是一个普通文件或者文件夹 (这里的文件夹通常叫做目录,但是一个目录中可以有文件,也可以有目录) 【/】:Linux下路径分隔符 【\】:windows下路径分隔符 【问题与答案】 1.【为什么要有路径?】 路径是系统层标识一个特定的文件,路径分为绝对路径和相对路径,在Linux的整个文件目录结构是一个多叉树,属于树状结构 根据树状结构,从中可以知道每个孩子(子文件)都只有一个父目录,这也导致了路径必须具有唯一性 ,最开始的目录称为根目录 2.【为什么要找到目标文件?】 访问任何文件之前,都必须先找到这个文件,为了找到这个目标文件,所以需要使用到目录 1.7 .与…用法 1.7.1 隐藏文件 无论是在Linux下还是在Windows下,都有隐藏文件存在。 【Windows下隐藏文件】 【Linux下隐藏文件】 关于查看Linux目录中隐藏文件,我们需要使用ls -l或ll指令进行查看,该指令作用是更详细罗列目录下所有子目录和文件信息,而ls -la是列出目录下的所有文件,包括以 . 开头的隐含文件。任何目录下,都会默认具有两个隐藏目录.和… 1.7.2 .当前用法 【.两种用法】: 可以表示当前路径 指定执行当前目录下的一个可执行文件,表示明确该文件在该目录下,可以直接执行该可执行文件 1.7.3 …当前用法 【…用法】: 表示上级路径,可以方便我们进行路径的回退。毕竟不光要进去,也要可以出来 【价值体现】 在Linux的整个文件目录结构是一个多叉树,是属于树状结构的,那么可以灵活地使用…返回上一级路径配合相对路径和绝对路径进行在Linux的整个文件目录下就行游走。 二、常见指令介绍 前言Linux的指令和与之对应的常用选项很多,那么下面是一些常见的使用,下列大约有二十个指令,一开始记不住,之后忘不掉,不用死背指令,见多就记住了。这里介绍途中会混合一些小指令,知道如何使用和作用即可,附加一些周边知识,接下来将正式开始。 2.1 pwd指令 【语法】:pwd 【功能】:显式用户当前所在的目录 2.2 cd指令 Linux系统中,磁盘上的文件和目录被组成一颗目录树,每个节点都是目录或文件。 【语法】:cd 目录名(不是文件名) 【功能】:改变工作目录,将当前工作目录改变到指定目录下 【返回上级目录】:cd … 【以绝对路径跳转目录】:cd /home/litao/linux/ 【以相对路径跳转目录】:cd …/day02/ 【进入用户家目】:cd ~ 【返回最近访问目录】:cd - 【cd ~ 使用介绍】 【作用】: 跳转到我们最近一次所处的路径下,这有助于我们是实现处理两个路径的快速切换 【cd - 使用介绍】 【作用】: 进入用户家目录 2.3 家目录 家目录(Home Directory)是操作系统为每个用户分配的一个专用目录,用于存储该用户的个人文件、配置文件和数据 【windows中的用户默认的家目录】 C:\Users\用户名 【Linux下指定用户的家目录】 对于root账号:默认的家目录/root—>超级管理员账号 对于普通用户:默认的家目录/home/新建的用户名 关于以上两点,任何一个用户,首次登录所处的路径都是自己的家目录,关于这点可以使用whoami指令查看当前正在使用Linux系统的用户名 2.4 whoami指令 whoami指令查看当前正在使用Linux系统的用户名,在# 表达的时候,经常说我们在XXX路径下【“我们” -whoami】。 2.5 重新认识指令 目前阶段来说指令的本质都是程序。指令、程序、可执行程序都是一回事,并且也是文件。 2.5.1 安装和卸载行为含义 安装和卸载就是把可执行程序拷贝/删除到系统路径下 2.6 which指令 which指令要求系统打印出我所制定的指令名称在系统中所在路径位置 那么我们可以根据which指令,得到ll和ls -l指令之间存在某种关系。 2.7 alias指令 alias也是一个Linux指令,给其他命令起一个别名。 目前不建议大家使用该指令为其他指令取别名,由于目前指令接触不多,很容易导致混乱。 关于--color == auto(auto可省略),这里就是是否带上颜色。 2.8 ls指令 【语法】:ls [选项] [目录或文件] 【功能】:对于目录,该命令列出该目录下的所有子目录于文件。对于文件,将列出文件名以及其他信息 【常用选择】:主要掌握-d -l -a选项,剩下有需要记 [-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.9 touch指令 【语法】:touch [选项]… 文件… 【功能】:touch命令参数可改变文档或目录的时间,包括存储时间和更改时间,或者新建一个不存在的文件 【常用选项】[有需要记]: -a 或–time=atime或–time=access或–time=use只更改存取时间。 -c 或–no-create 不建立任何文档。 -d 使用指定的日期时间,而非现在的时间。 -f 此参数将忽略不予处理,仅负责解决BSD版本touch指令的兼容性问题。 -m 或–time=mtime或–time=modify 只更改变动时间。 -r 把指定文档或目录的日期时间,统统设成和参考文档或目录的日期时间相同。 -t 使用指定的日期时间,而非现在的时间 2.10 stat指令与文件ACM时间 可以使用stat指令进行查看,文件或目录的不同时间戳 【Access Time (atime)】:文件最后一次被访问(读取)的时间。 【Modify Time (mtime)】:文件内容最后一次被修改的时间。 【Change Time (ctime)】:文件元数据(如权限)最后一次被修改的时间。 2.11 mkdir指令(重要) 【语法】:mkdir[选项] dirname… 【功能】:在当前目录下创建一个名为"dirname"的目录 【常用选项】: [-p (parent )] :可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后系统将自动建立好那些尚不存在的目录,**既一次性建立多个目录。**如果没有添加-p选项,就不能一次性建立多个目录。 ———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2302_79177254/article/details/142755397
推荐直播
-
华为云码道 × 仓颉编程:工程化AI编码探索2026/05/27 周三 19:00-21:00
刘俊杰-华为云仓颉语言专家/李炎-华为云码道技术专家/王智鹏-OpenCangjie开源社区发起人
本场直播围绕华为云仓颉语言与华为云码道的深度结合,展示华为云智能编程从零基础到高效落地的完整生态能力。以华为云码道为引擎,仓颉语言为载体,带给大家日常提效、趣味创新到极速量产的开发体验。
回顾中
热门标签