• [技术干货] Linux主机定时任务创建与管理深度指南
    Linux主机定时任务创建与管理深度指南引言在Linux系统中,定时任务是一项非常实用的功能,它允许用户在指定的时间自动执行脚本或命令。无论是进行系统备份、日志清理,还是执行定期维护任务,定时任务都能大大简化管理员的工作。本文将深入探讨Linux主机上定时任务的创建与管理,包括crontab工具的使用、定时任务的配置方法、以及一些高级管理和排错技巧。通过本文,你将学会如何充分利用Linux的定时任务功能,提高工作效率。一、Linux定时任务基础1. 什么是定时任务定时任务,顾名思义,就是在特定的时间点或时间间隔内自动执行的任务。在Linux系统中,这通常通过crontab(cron table)来实现。cron是Linux下的一个系统守护进程,用于按照预定的时间计划执行任务。2. crontab简介crontab是用户用来设置周期性被执行的任务的列表。每个用户都可以有自己的crontab文件,用于定义该用户的定时任务。系统级别的定时任务则通常配置在/etc/crontab文件或/etc/cron.d/目录下的文件中。二、创建和管理定时任务1. 安装与启动cron服务在大多数Linux发行版中,cron服务是默认安装的。你可以通过以下命令检查cron服务是否正在运行:systemctl status cron # 或者对于使用systemd的系统 systemctl status crond如果服务未运行,可以使用以下命令启动它:systemctl start cron # 或者 systemctl start crond2. 编辑crontab文件要为用户设置定时任务,可以使用crontab -e命令。这将打开一个文本编辑器(通常是vi或nano),允许你编辑该用户的crontab文件。crontab -ecrontab文件的每一行都代表一个定时任务,格式如下:* * * * * command_to_be_executed - - - - - | | | | | | | | | +----- 星期几 (0 - 7) (星期天用0或7表示) | | | +------- 月份 (1 - 12) | | +--------- 日期 (1 - 31) | +----------- 小时 (0 - 23) +------------- 分钟 (0 - 59) 例如,要每天凌晨2点执行一个备份脚本,可以添加以下行:0 2 * * * /path/to/backup_script.sh3. 查看和管理crontab任务查看当前用户的定时任务:crontab -l删除当前用户的所有定时任务:crontab -r以root用户身份编辑/查看/删除定时任务:使用sudo前缀来执行上述命令,例如:sudo crontab -e sudo crontab -l sudo crontab -r4. 系统级别的定时任务除了用户级别的crontab文件外,Linux系统还支持系统级别的定时任务配置。这些任务通常定义在以下文件中:/etc/crontab:系统级别的crontab文件,可以为不同用户设置任务。/etc/cron.d/:这个目录下的每个文件都可以被视为一个独立的crontab文件。/etc/cron.hourly/、/etc/cron.daily/、/etc/cron.weekly/、/etc/cron.monthly/:这些目录下的脚本会分别在每小时、每天、每周、每月的特定时间点执行。三、定时任务的高级管理1. 使用特殊字符串简化时间设置在crontab中,可以使用一些特殊字符串来简化时间设置,例如:@reboot:系统启动时运行@yearly 或 @annually:每年运行一次(0 0 1 1 *)@monthly:每月运行一次(0 0 1 * *)@weekly:每周运行一次(0 0 * * 0)@daily 或 @midnight:每天运行一次(0 0 * * *)@hourly:每小时运行一次(0 * * * *)例如,要每天运行一次日志清理脚本,可以使用:@daily /path/to/log_cleanup_script.sh2. 定时任务的输出重定向默认情况下,cron任务的输出(包括标准输出和标准错误输出)会通过电子邮件发送给任务的所有者。如果不想接收这些邮件,可以将输出重定向到文件或丢弃:0 2 * * * /path/to/backup_script.sh > /dev/null 2>&1 上述命令将备份脚本的输出丢弃,避免产生不必要的邮件。3. 环境变量与路径问题在cron任务中,环境变量可能与用户的交互式shell不同。因此,建议在使用绝对路径来指定脚本和命令,并确保脚本内部也使用绝对路径或适当设置环境变量。例如:0 2 * * * /usr/bin/env bash /path/to/backup_script.sh或者使用脚本内部的绝对路径:#!/bin/bash # backup_script.sh TARBALL="/path/to/backup/$(date +\%Y\%m\%d).tar.gz" /bin/tar -czf $TARBALL /path/to/data4. 定时任务的排错与日志查看cron日志:cron的日志通常记录在/var/log/cron或/var/log/syslog文件中(取决于系统配置)。可以通过查看这些日志来排查定时任务的问题。grep CRON /var/log/syslog增加调试信息:在脚本中添加调试信息,如日志输出,可以帮助追踪定时任务的执行情况。#!/bin/bash # debug_script.sh echo "Script started at $(date)" >> /path/to/debug.log # ... your script logic here ... echo "Script finished at $(date)" >> /path/to/debug.log四、定时任务的安全与管理最佳实践1. 限制cron访问使用/etc/cron.allow和/etc/cron.deny:可以通过这两个文件来控制哪些用户可以使用cron服务。/etc/cron.allow具有更高优先级,如果该文件存在,则只有列在该文件中的用户可以使用cron;否则,/etc/cron.deny文件中的用户将被禁止使用cron。2. 定时任务的权限管理确保脚本具有适当的权限:定时任务执行的脚本应该具有可执行权限,并且应该限制为只有必要的用户可以访问和修改。chmod 700 /path/to/sensitive_script.sh chown root:root /path/to/sensitive_script.sh避免使用root用户设置不必要的定时任务:尽量减少使用root用户设置定时任务,以降低系统安全风险。如果必须使用root权限,应确保脚本的安全性和可靠性。3. 定时任务的监控与报警设置监控和报警机制:可以使用监控工具(如Nagios、Zabbix等)来监控定时任务的执行情况,并在任务失败或未按时执行时发送报警。定期检查定时任务日志:应定期检查定时任务的日志,以确保任务正常运行并及时发现潜在问题。五、定时任务实战案例案例一:定期备份数据库假设我们有一个MySQL数据库,需要每天凌晨3点进行备份。可以编写一个备份脚本,并使用cron来定期执行该脚本。备份脚本(mysql_backup.sh):#!/bin/bash BACKUP_DIR="/path/to/backups" DATE=$(date +\%Y\%m\%d) MYSQL_USER="root" MYSQL_PASSWORD="your_password" MYSQL_DATABASE="your_database" mkdir -p $BACKUP_DIR mysqldump -u$MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE > $BACKUP_DIR/${MYSQL_DATABASE}_$DATE.sql设置定时任务:crontab -e # 添加以下行 0 3 * * * /path/to/mysql_backup.sh案例二:定期清理临时文件假设我们需要每天凌晨1点清理/tmp目录下的临时文件(超过7天未访问的文件)。清理脚本(tmp_cleanup.sh):#!/bin/bash find /tmp -type f -atime +7 -exec rm {} \; 设置定时任务:crontab -e # 添加以下行 0 1 * * * /path/to/tmp_cleanup.sh
  • [技术干货] 磐维数据库主备切换详解
    磐维数据库主备切换详解一、引言在数据库系统中,主备切换是一个至关重要的功能,它确保了数据库的高可用性和数据的安全性。当主数据库出现故障时,能够迅速将备数据库切换为主数据库,从而保证业务的连续性。磐维数据库作为中国移动自研的高性能数据库产品,其主备切换功能具有高效、稳定、可靠的特点。二、磐维数据库主备切换概述1. 主备切换的定义主备切换是指在数据库主备架构中,当主数据库出现故障或需要维护时,将备数据库切换为主数据库,以继续提供数据库服务的过程。主备切换通常包括自动切换和手动切换两种方式。2. 磐维数据库主备切换的特点高效性:磐维数据库的主备切换过程快速,能够迅速将备数据库切换为主数据库,减少业务中断时间。稳定性:磐维数据库的主备切换机制稳定可靠,确保切换过程中数据的完整性和一致性。灵活性:磐维数据库支持多种主备切换模式,包括自动切换和手动切换,满足不同场景下的需求。三、磐维数据库主备切换机制1. 主备架构磐维数据库的主备架构通常包括一个主数据库和一个或多个备数据库。主数据库负责处理业务请求,备数据库则通过复制主数据库的数据来保持与主数据库的数据一致性。当主数据库出现故障时,备数据库可以迅速切换为主数据库,继续提供数据库服务。2. 复制机制磐维数据库采用基于日志的复制机制,主数据库将事务日志发送给备数据库,备数据库通过应用这些日志来保持与主数据库的数据一致性。这种复制机制确保了主备数据库之间的数据同步,为主备切换提供了基础。3. 切换决策磐维数据库的主备切换决策通常由数据库管理系统(DBMS)根据主数据库的状态和预设的切换策略来做出。当主数据库出现故障或需要维护时,DBMS会触发切换机制,将备数据库切换为主数据库。四、磐维数据库主备切换步骤1. 计划内切换步骤计划内切换是指在预期的时间内进行的主备切换,通常用于数据库维护或升级等场景。计划内切换步骤包括:集群状态检查:在执行切换之前,需要检查主备集群的状态,确保所有节点都处于正常状态。gs_om -t status --detail该命令用于检查集群状态,输出详细的集群信息。停止主业务:在切换之前,需要停止主数据库上的业务请求,以防止数据不一致的问题。主备集群切换:执行主备切换命令,将备数据库切换为主数据库。gs_sdr -t switchover -m disaster_standby # 主集群任一节点执行 gs_sdr -t switchover -m primary # 备集群任一节点执行(不需要等待主集群执行完毕) 集群状态检查:切换完成后,需要再次检查集群状态,确保新主数据库正常运行。2. 非计划内切换步骤非计划内切换是指在主数据库出现故障时进行的紧急切换。非计划内切换步骤包括:故障检测:DBMS通过心跳检测、日志分析等方式检测主数据库是否出现故障。自动切换或手动切换:当检测到主数据库出现故障时,DBMS会触发自动切换机制(如果配置了自动切换)或提示管理员进行手动切换。自动切换:DBMS根据预设的切换策略自动选择备数据库切换为主数据库。手动切换:管理员通过执行特定的命令将备数据库切换为主数据库。gs_ctl failover [-D DATADIR] # 在备集群主节点执行 gs_sdr -t failover # 在备集群主节点执行 业务恢复:切换完成后,需要确保新主数据库能够正常处理业务请求,并进行必要的数据验证和修复工作。五、磐维数据库主备切换代码示例1. 计划内切换代码示例假设我们有一个主备集群,主数据库节点为1,备数据库节点为2。以下是进行计划内切换的代码示例:-- 在主集群节点1上执行 gs_sdr -t switchover -m disaster_standby -- 在备集群节点2上执行 gs_sdr -t switchover -m primary切换完成后,可以使用以下命令检查集群状态:gs_om -t status --detail2. 非计划内切换代码示例假设主数据库节点1出现故障,需要进行紧急切换。以下是进行非计划内切换的代码示例:-- 在备集群主节点2上执行 gs_ctl failover [-D DATADIR] -- 或者使用gs_sdr工具 gs_sdr -t failover切换完成后,同样需要使用以下命令检查集群状态:gs_om -t status --detail六、磐维数据库主备切换的最佳实践1. 配置合理的切换策略根据业务需求和数据库环境,配置合理的切换策略。例如,可以配置自动切换的阈值条件(如主数据库故障时间、故障类型等),以及手动切换的触发条件(如管理员通知、特定时间窗口等)。2. 定期演练切换流程定期进行主备切换演练,确保切换流程顺畅无误。演练过程中可以模拟各种故障场景,如主数据库宕机、网络故障等,以检验切换机制的有效性和可靠性。3. 监控和告警建立完善的监控和告警机制,实时监控主备数据库的状态和性能指标。当检测到异常情况时,及时发出告警通知管理员进行处理。4. 数据验证和修复切换完成后,对新主数据库进行必要的数据验证和修复工作。例如,可以比较主备数据库之间的数据差异,修复不一致的数据记录等。5. 性能优化对主备切换过程进行性能优化,减少切换时间和资源消耗。例如,可以优化复制机制、切换决策算法等,提高切换效率和稳定性。七、案例分析假设某企业部署了一套磐维数据库主备集群,主数据库节点为1,备数据库节点为2。在某天晚上,主数据库节点1突然出现故障,导致业务中断。此时,管理员按照以下步骤进行了非计划内切换:故障检测:DBMS通过心跳检测机制检测到主数据库节点1出现故障。手动切换:管理员立即登录到备集群主节点2,执行gs_ctl failover [-D DATADIR]命令进行手动切换。状态检查:切换完成后,管理员使用gs_om -t status --detail命令检查集群状态,确认新主数据库节点2已经正常运行。业务恢复:管理员通知业务团队进行业务恢复操作,确保新主数据库能够正常处理业务请求。数据验证和修复:管理员对新主数据库进行必要的数据验证和修复工作,确保数据的完整性和一致性。通过这次切换操作,该企业成功避免了业务中断的风险,并验证了磐维数据库主备切换机制的有效性和可靠性。
  • [技术干货] 磐维数据库语法入门介绍
    磐维数据库语法入门介绍一、引言磐维数据库(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系统的端口管理,包括端口的基本概念、查看端口状态、开放和关闭端口、端口扫描、性能优化以及安全防护等方面的内容。一、端口的基本概念在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系统的端口管理是网络配置和管理中的重要组成部分。通过了解端口的基本概念、查看端口状态、开放和关闭端口、端口扫描、性能优化以及安全防护等方面的内容,可以有效地提高系统的网络性能和安全性。在实际操作中,应根据具体需求和环境选择合适的工具和策略来管理端口。同时,也应保持对新技术和新工具的关注和学习,以便更好地应对不断变化的网络安全挑战。
  • 对比ARM架构和X86架构的指令集
    指令集设计理念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 架构的深度剖析
    在 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 主机时做出更加明智的决策。如果你对这两种架构还有其他的疑问或者想法,欢迎在评论区留言讨论。
  • [互动交流] 鲲鹏916和鲲鹏920有AArch32支持吗?
    我目前手上有一个棘手的需求,需要在服务器上利用KVM运行32位的Linux操作系统。通过搜索网络我发现许多64位的ARM处理器已经不具备32位的支持,或者仅仅在个别EL下支持32位。我正在寻找具备EL1和EL0下的32位支持、且具备虚拟化扩展的64位ARM处理器。请问有使用过搭载鲲鹏916和鲲鹏920处理器的服务器的同行吗?鲲鹏916和鲲鹏920是否具备EL0和EL1下的32位支持?
  • [技术干货] ExaGear for Server on Ubuntu
    安装安装前,检查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_进程概念-转载
    📢前言在 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
  • [互动交流] 安装固件报错,找不到PCIE设备
    安装固件报错,找不到PCIE设备
  • [互动交流] HiLens支持从SD卡启动吗?
    SD卡烧录好欧拉系统,HiLens能够设置SD卡为启动首选项,使用SD卡烧录的系统吗?备注:HiLens根目录只有2GB,空间太少了,安装几个库就没有空间了,总计32GB的空间,分成了很多个区,不敢贸然合并,所以想搞个大容量SD卡,烧录系统使用
  • [技术干货] Linux下shell基本命令之grep用法及示例小结【转】
    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/
  • [技术干货] Linux内核验证套件(LKVS)
     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之权限】我们详细地了解了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 
  • [技术干货] 【Linux】线程同步与互斥 (生产者消费者模型-转载
     一:🔥 线程互斥 🦋 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 
总条数:1199 到第
上滑加载中