linux内核信号量和互斥锁使用
信号量概念Linux 内核的信号量在概念和原理上与用户态的 System V 的 IPC 机制信号量是一样的但是它绝不可能在内核之外使用因此它与 System V 的 IPC 机制信号量毫不相干。信号量在创建时需要设置一个初始值表示同时可以有几个任务可以访问该信号量保护的共享资源初始值为 1 就变成互斥锁Mutex即同时只能有一个任务可以访问信号量保护的共享资源。一个任务要想访问共享资源首先必须得到信号量获取信号量的操作将把信号量的值减 1若当前信号量的值为负数表明无法获得信号量该任务必须挂起在该信号量的等待队列等待该信号量可用若当前信号量的值为非负数表示可以获得信号量因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后必须释放信号量释放信号量通过把信号量的值加 1 实现如果信号量的值为非正数表明有任务等待当前信号量因此它也唤醒所有等待该信号量的任务。内核信号量核心结构内核中使用 struct semaphore 结构来描述一个信号量结构定义如下struct semaphore { raw_spinlock_t lock; unsigned int count; //信号值只要这个值为正数则信号可用 一般情况设置 0 或 1。 struct list_head wait_list; };信号量相关 APIDEFINE_SEMAPHORE (name)宏原型#define DEFINE_SEMAPHORE(name) \struct semaphore name __SEMAPHORE_INITIALIZER(name, 1)宏功能该宏声明一个信号量 name 并初始化它的值为 1即声明一个互斥锁宏参数name 要定义的信号量变量名所在头文件include\linux\semaphore.h宏定义文件include\linux\semaphore.h备注Linux3.5 内核出现 在 linux 2.6 中名字为 DECLARE_MUTEX(name)void sema_init(struct semaphore *sem, int val)宏函数 原型void sema_init(struct semaphore *sem, int val)宏功能该函数用于初始化一个互斥锁即它把信号量 sem 的值设置为 1。宏参数sem 要初始化的信号量的指针所在头文件include\linux\semaphore.h宏定义文件include\linux\semaphore.h备注void sema_init (struct semaphore *sem, int val);宏原型void sema_init (struct semaphore *sem, int val);宏功能初始化设置信号量的初值它设置信号量 sem 的值为 val宏参数sem 要初始化的信号量的指针val: 信号量的值所在头文件include\linux\semaphore.h宏定义文件include\linux\semaphore.h定义、初始化信号量相关 API:这是一个静态定义方式。 定义一个名字为 name,信号量值为 1 的信号量结构变量。示例struct semaphore sem;sema_init(sem, 1);以上两行等效DEFINE_SEMAPHORE(sem);void down(struct semaphore *sem)函数原型void down(struct semaphore * sem);函数功能用于获得信号量 sem它会导致睡眠因此不能在中断上下文包括 IRQ 上下文和 softirq 上下文使用该函数。该函数将把 sem 的值减 1如果信号量 sem 的值非负就直接返回否则调用者将被挂起直到别的任务释放该信号量才能继续运行。函数参数sem 要初始化获取的信号量的指针函数返回值无所在头文件include\linux\semaphore.h函数定义文件kernel\semaphore.cint down_timeout(struct semaphore *sem, long jiffies);sem 信号量结构指针jiffies 要设置超时时间单位是时钟节拍。阻塞地请求一个信号量,如果信号量大于 0 则可以马上返回 否则休眠直到有其他进程释放信号量把信号量的 count 修改为大于 0 的值 或超时时间到。这个函数效果和 down 函数很像它也是不可中断的休眠。int down_interruptible(struct semaphore * sem);函数原型int down_interruptible(struct semaphore * sem);函数功能该函数功能与 down 类似不同之处为 down 不会被信号signal打断但 down_interruptible能被信号打断因此该函数有返回值来区分是正常返回还是被信号中断如果返回 0表示获得信号量正常返回如果被信号打断返回-EINTR。函数参数sem 要获取的信号量的指针函数返回值0得到信号量正常返回 负数被信号中断返回所在头文件include\linux\semaphore.h函数定义文件kernel\semaphore.cint down_trylock(struct semaphore * sem);函数原型int down_trylock(struct semaphore * sem);函数功能该函数试着获得信号量 sem如果能够立刻获得它就获得该信号量并返回 0否则表示不能获得信号量 sem返回值为非 0 值。因此它不会导致调用者睡眠可以在中断上下文使用。函数参数sem 要获取的信号量的指针函数返回值0得到信号量正常返回 非 0得不到信号量所在头文件include\linux\semaphore.h函数定义文件kernel\semaphore.cvoid up(struct semaphore * sem);函数原型void up(struct semaphore * sem);函数功能该函数释放信号量 sem即把 sem 的值加 1如果 sem 的值为非正数表明有任务等待该信号量因此唤醒这些等待者。函数参数sem 要初释放的信号量的指针函数返回值无头文件include\linux\semaphore.h函数定义文件kernel\semaphore.c虽然信号量可以设置为大于 1 的值但是信号量在绝大部分情况下作为互斥锁使用。简单信号量使用例子实现设备只能被一个进程打开#include linux/module.h /* Needed by all modules */ #include linux/init.h /* Needed for the module-macros */ #include linux/fs.h #include linux/miscdevice.h #include asm/atomic.h #define LEDS_MAJOR 255 #define DEVICE_NAME miscdev_semaphore DECLARE_MUTEX(lock); //定义一个互斥信号量并初始化 1; static int first_miscdev_open(struct inode *pinode, struct file *pfile) { printk (KERN_EMERG Linux miscdevice:%s is call\r\n,__FUNCTION__); /* 获取信号量 */ if (!down_trylock(lock)) return 0; else return -EBUSY; } int first_miscdev_release (struct inode *inode, struct file *file) { printk (KERN_EMERG Linux miscdevice:%s is call\r\n,__FUNCTION__); up(lock); //释放信号量 return 0; } static struct file_operations dev_fops { .owner THIS_MODULE, .open first_miscdev_open, .release first_miscdev_release, }; static struct miscdevice misc { .minor LEDS_MAJOR, .name DEVICE_NAME, .fops dev_fops, }; /* 模块装载执行函数 */ static int __init first_miscdev_init(void) { int ret; ret misc_register(misc); //注册混杂设备 if(ret 0) printk (KERN_EMERG DEVICE_NAME\t err\r\n); printk (KERN_EMERG DEVICE_NAME\tinitialized\n); return ret; } /* 模块卸载执行函数 */ static void __exit first_miscdev_exit(void) { misc_deregister(misc); printk(KERN_EMERG Goodbye,cruel world!, priority 0\n); } module_init(first_miscdev_init); module_exit(first_miscdev_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(XYD); MODULE_DESCRIPTION(This the samlpe drv_test);互斥信号量互斥信号量概念互斥锁主要用于实现内核中的互斥访问功能。内核互斥锁是在原子 API 之上实现的但这对于内核用户是不可见的。 在大部分场合中对共享资源的访问都是使用独占方式 在这种情况下 提供专门的互斥接口给编程者使用。 对它的访问必须遵循一些规则同一时间只能有一个任务持有互斥锁而且只有这个任务可以对互斥锁进行解锁。互斥锁不能进行递归锁定或解锁。一个互斥锁对象必须通过其 API 初始化而不能使用 memset 或复制初始化。一个任务在持有互斥锁的时候是不能结束的。互斥锁所使用的内存区域是不能被释放的。使用中的互斥锁是不能被重新初始化的。并且互斥锁不能用于中断上下文。但是互斥锁比当前的内核信号量选项更快并且更加紧凑因此如果它们满足您的需求那么它们将是您明智的选择。内核信号量核心结构内核中使用 struct mutex 结构来描述一个信号量结构定义如下struct mutex { /* 1: unlocked, 0: locked, negative: locked, possible waiters */ atomic_t count; spinlock_t wait_lock; struct list_head wait_list; #if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP) struct task_struct *owner; #endif #ifdef CONFIG_DEBUG_MUTEXES const char *name; void *magic; #endif #ifdef CONFIG_DEBUG_LOCK_ALLOC struct lockdep_map dep_map; #endif };关键成员说明atomic_t count指示互斥锁的状态1--没有上锁可以获得0--被锁定不能获得负数--被锁定且可能在该锁上有等待进程初始化时为没有上锁状态。spinlock_t wait_lock等待获取互斥锁中使用的自旋锁。在获取互斥锁的过程中操作会在自旋锁的保护中进行。初始化为为锁定 用户一般无需关心。struct list_head wait_list等待互斥锁的进程队列 用户无需关心。互斥信号量相关 APIDEFINE_MUTEX(mutexname)宏原型#define DEFINE_MUTEX(mutexname) \struct mutex mutexname __MUTEX_INITIALIZER(mutexname)宏功能该宏声明一个互斥信号量 name 并初始化它宏参数name 要定义的互斥信号量变量名所在头文件include\linux\mutex.h备注linux3.5出现mutex_init(mutex)宏原型# define mutex_init(mutex) \do { \static struct lock_class_key __key; \__mutex_init((mutex), #mutex, __key); \} while (0)宏功能该宏初始化一个已经定义的互斥信号量 metex 传递的是指针宏参数mutex 初始化的的互斥信号量指针所在头文件include\linux\mutex.h宏定义文件include\linux\mutex.hint mutex_is_locked(struct mutex *lock)宏原型int mutex_is_locked(struct mutex *lock)宏功能该函数检测互斥锁是否已经被锁定宏参数lock 互斥锁变量是指针返回值1 已经锁定 0 没有锁定所在头文件include\linux\mutex.hmutex_lock(lock) 或 void mutex_lock(struct mutex *lock);宏函数 原型mutex_lock(lock)或void mutex_lock(struct mutex *lock);宏功能该宏函数 获取互斥锁是否 如没有得到会休眠直接得到为止宏参数lock 互斥锁变量是指针返回值无所在头文件include\linux\mutex.h宏定义文件include\linux\mutex.h备注根据内核配置不同该功能有有宏版本和函数版本mutex_lock_interruptible(lock) 或 int mutex_lock_interruptible(struct mutex *lock);宏原型#define mutex_lock_interruptible(lock) mutex_lock_interruptible_nested(lock, 0)或int __must_check mutex_lock_interruptible(struct mutex *lock);宏功能该宏函数获取互斥锁是否 如没有得到会休眠直接得到为止 但是可以被信号中断宏参数lock 互斥锁变量是指针返回值成功获得锁返回 0 被信号中断返回-EINIR所在头文件include\linux\mutex.h宏定义文件include\linux\mutex.h备注根据内核配置不同该功能有有宏版本和函数版本int mutex_trylock(struct mutex *lock);宏原型int mutex_trylock(struct mutex *lock);宏功能该函数是获取互斥锁 如没有得到不会休眠马上返回宏参数lock 互斥锁变量是指针返回值无所在头文件include\linux\mutex.h宏定义文件include\linux\mutex.c备注根据内核配置不同该功能有有宏版本和函数版本void mutex_unlock(struct mutex *lock);宏原型void mutex_unlock(struct mutex *lock);宏功能该函数是释放互斥锁宏参数lock 互斥锁变量是指针返回值无所在头文件include\linux\mutex.h宏定义文件include\linux\mutex.c互斥锁应用例子/* chardev.c */ #include linux/module.h /* Needed by all modules */ #include linux/init.h /* Needed for the module-macros */ #include linux/fs.h #include linux/cdev.h #include linux/device.h /* 自动创建设备文件 */ #include linux/miscdevice.h #include asm/io.h /*ioremap*/ #include asm/uaccess.h /*copy_from_user ,copy_to_user*/ #include linux/atomic.h #include linux/mutex.h #include linux/gpio.h #include mach/gpio.h #include plat/gpio-cfg.h /* s3c_gpio_cfgpin S3C_GPIO_OUTPUT */ static struct cdev *pcdev; static struct device *this_device NULL; static struct class *leds_class NULL; /* 是否开启 gpio_request 函数功能 */ #define GPIO_REQUEST 1 //0 :关,非0 :开 static int led_gpios[] { EXYNOS4X12_GPM4(0), EXYNOS4X12_GPM4(1), EXYNOS4X12_GPM4(2), EXYNOS4X12_GPM4(3), }; #define LED_NUM ARRAY_SIZE(led_gpios) #define DEV_NAME myleds unsigned int major 0; //主设备号 unsigned int minor 0; //次设备号 unsigned int devnr 0; //设备号 char *chrdev_name DEV_NAME; //设备名 module_param(major, int, S_IRUGO | S_IWUSR); module_param(minor, int, S_IRUGO | S_IWUSR); module_param(chrdev_name, charp, S_IRUGO | S_IWUSR); struct mutex lock; //打开设备时候执行的程序 static int chrdev_open(struct inode *pinode, struct file *pfile) { mutex_lock(lock); printk(KERN_EMERG line:%d,%s is call\n, __LINE__, __FUNCTION__); return 0; } //关闭设备时执行的程序 static int chrdev_release(struct inode *pinode, struct file *pfile) { mutex_unlock(lock); printk(KERN_EMERG line:%d,%s is call\n, __LINE__, __FUNCTION__); return 0; } //读接口函数 static ssize_t chrdev_read ( struct file *file, char __user *buf, size_t count, loff_t * f_pos ) { char led_buffer[4] {2, 2, 2, 2}; int i 0; // printk ( KERN_EMERG line:%d,%s is call\n, __LINE__, __FUNCTION__ ); /* count 0 ,则返回0不是错误 */ if ( !count ) { return 0; } /* 当前已经到文件末尾不能再写返回0,*/ if ( count LED_NUM ) { count LED_NUM; } /* 准备数据0表示灭1表示亮 */ for (i 0; i LED_NUM; i) { led_buffer[i] !gpio_get_value(led_gpios[i]); } if ( copy_to_user ( buf, led_buffer[0], count ) ) { return -EFAULT; } return count; } //写接口函数 static ssize_t chrdev_write(struct file *pfile, const char __user *user_buf, size_t count, loff_t *off) { int ret 0; char buf[LED_NUM] {0}; int i 0; // printk(KERN_EMERG line:%d,%s is call\n, __LINE__, __FUNCTION__); //应用程序传递count0下来并不是错误应该返回0 if(count 0) { return 0; } //因为板子只有4个灯所以防止用户程序恶意破坏系统。 if(count LED_NUM) { count LED_NUM; } //把用户空间传递下来的数据复制到内核空间的buf数组中 ret copy_from_user(buf, user_buf, count); //返回成功是0其他是失败 if(ret) { //如果复制失败返回-1; return -EFAULT; } for(i 0; i count; i) { if(buf[i] 1) // GPM4DAT ~(1 (0 i));/* 亮 */ { gpio_set_value(led_gpios[i], 0); } else if(buf[i] 0) // rGPM4DAT | (1 (0 i));/* 灭 */ { gpio_set_value(led_gpios[i], 1); } } count 1; return count; } //文件操作方法 static const struct file_operations chrdev_fops { .read chrdev_read, .write chrdev_write, .release chrdev_release, .open chrdev_open, }; static int __init chrdev_init(void) { int ret 0; int i; /* IO口配置代码 */ for (i 0; i LED_NUM; i) { #if GPIO_REQUEST ret gpio_request(led_gpios[i], LED); if (ret) { printk(%s: request GPIO %d for LED failed, ret %d\n, chrdev_name, led_gpios[i], ret); goto gpio_request_err; } #endif //s3c_gpio_cfgpin(led_gpios[i], S3C_GPIO_OUTPUT); //gpio_set_value(led_gpios[i], 1); //gpio_direction_output等效于上面两条语句, gpio_direction_output(led_gpios[i], 1); } //分配cdev核心结构; pcdev cdev_alloc(); if (pcdev NULL) { ret -ENOMEM; printk(KERN_EMERGcdev_alloc_err error\n); goto cdev_alloc_err; } //如果主设备号是0,则动态分配设备号 if(!major) { ret alloc_chrdev_region(devnr, minor, 1, chrdev_name); if(ret 0) { printk(KERN_EMERGalloc_chrdev_region error\n); goto devnr_requst_err; } } else { //合成设备号 devnr MKDEV(major, minor); //静态注册设备号 ret register_chrdev_region(devnr, 1, chrdev_name); if(ret ! 0) { printk(KERN_EMERGregister_chrdev_region error\n); goto devnr_requst_err; } } //cdev结构体初始化 cdev_init(pcdev, chrdev_fops); //注册字符设备 ret cdev_add(pcdev, devnr, 1); if ( ret 0) { printk(KERN_EMERGcdev_add error\n); goto cdev_add_err; } //输出字符设备主设备号和次设备号 printk(KERN_EMERG name:%s,major:%d,minor:%d\n, chrdev_name, MAJOR(devnr), MINOR(devnr)); //创建一个类 leds_class class_create(THIS_MODULE, leds_class); if ( IS_ERR(leds_class) ) { ret PTR_ERR(leds_class); printk(KERN_EMERGclass_create error\n); goto class_create_err; } //创建一个设备 this_device device_create(leds_class, NULL, devnr, NULL, %s, chrdev_name); if ( IS_ERR(this_device) ) { ret PTR_ERR(this_device); printk(KERN_EMERGthis_device error\n); goto device_create_err; } mutex_init(lock); return 0; device_create_err: class_destroy(leds_class); class_create_err: unregister_chrdev(major, chrdev_name); cdev_add_err: devnr_requst_err: if (pcdev) { cdev_del(pcdev); } unregister_chrdev_region(devnr, 1); cdev_alloc_err: #if GPIO_REQUEST gpio_request_err: /* gpio 反向释放 */ for ( --i ; i 0 ; i-- ) { gpio_free(led_gpios[i]); } #endif return ret; } static void __exit chrdev_exit(void) { int i 0; device_destroy(leds_class, devnr); class_destroy(leds_class); /* gpio 反向释放 */ for ( ; i 4 ; i) { gpio_free(led_gpios[i]); } cdev_del(pcdev); unregister_chrdev_region(devnr, 1); printk(KERN_EMERG Goodbye,chrdev\n); } module_init(chrdev_init); module_exit(chrdev_exit); MODULE_LICENSE(Dual BSD/GPL); MODULE_AUTHOR(learn); //optional MODULE_DESCRIPTION(STUDY_MODULE); //optional

相关新闻

GitHub 2FA 不是只填六位码:TOTP、Passkey、恢复方式与 PAT

GitHub 2FA 不是只填六位码:TOTP、Passkey、恢复方式与 PAT

GitHub 的账户安全需要拆成三层:网页登录的第二因素、账户恢复方式,以及 Git/API 使用的开发凭据。只配置验证器代码,不能自动解决恢复码、Passkey、Personal Access Token 或 SSH Key 的管理问题。 GitHub 2FA 的组成 GitHub 官方当前提供…

2026/9/23 4:26:21 阅读更多 →
PHP:数组截取与合并函数、数组拆分与连接函数:

PHP:数组截取与合并函数、数组拆分与连接函数:

数组截取与合并函数: 1.array_slice($arr,2,3); 从下标为2开始&#xff0c;截取3个值 数组截取.** <?php $arrarray(title1>linux,title2>java,title3>php,title4>js,title5>shell,title6>python,title7>ruby,title8>c, );$arr2array_slice($arr,…

2026/9/23 8:41:21 阅读更多 →
SQLite Viewer 终极指南:如何在线免费查看和导出SQLite数据库

SQLite Viewer 终极指南:如何在线免费查看和导出SQLite数据库

SQLite Viewer 终极指南&#xff1a;如何在线免费查看和导出SQLite数据库 【免费下载链接】sqlite-viewer View SQLite file online 项目地址: https://gitcode.com/gh_mirrors/sq/sqlite-viewer SQLite Viewer 是一款功能强大的在线 SQLite 文件查看器&#xff0c;让您…

2026/9/22 9:46:22 阅读更多 →

最新新闻

六大应用场景一:运动训练 - C 类(慢动作) :单腿站平衡、体态评估;运动监控(WIFI,蓝牙,USB)

六大应用场景一:运动训练 - C 类(慢动作) :单腿站平衡、体态评估;运动监控(WIFI,蓝牙,USB)

2026/9/23 9:50:27 阅读更多 →
Atlas 300V上部署YOLO:从模型转换到性能调优的完整实操指南

Atlas 300V上部署YOLO:从模型转换到性能调优的完整实操指南

最近好几个朋友都在问一件事&#xff1a;手里正好有一张 Atlas 300V 24G 的卡&#xff0c;能不能用来给线上的检测服务提速&#xff1f;还有人直接在搜索框里打“atlas 部署 yolo”&#xff0c;然后被一堆官方术语绕晕。我干脆把这一整套东西捋一遍。从这卡到底是个什么来路&am…

2026/9/23 9:50:27 阅读更多 →
Akka Streams `initialDelay` 操作符完全指南:源码实现与实战用法

Akka Streams `initialDelay` 操作符完全指南:源码实现与实战用法

Akka Streams initialDelay 操作符完全指南&#xff1a;源码实现与实战用法 【免费下载链接】akka-core A platform to build and run apps that are elastic, agile, and resilient. SDK, libraries, and hosted environments. 项目地址: https://gitcode.com/gh_mirrors/ak…

2026/9/23 9:50:27 阅读更多 →
Excel分类汇总的5个隐藏技巧与底层原理

Excel分类汇总的5个隐藏技巧与底层原理

1. 项目概述&#xff1a;为什么“分类汇总”总被当成鸡肋功能&#xff1f;Excel里有个功能叫“分类汇总”&#xff0c;很多人点开菜单扫一眼就关了——觉得它就是个自动求和的简化版数据透视表&#xff0c;甚至比不上手动筛选SUMIF组合来得灵活。我带过几十个财务、运营、供应链…

2026/9/23 9:50:27 阅读更多 →
CodeBurn 版本演进与技术架构解析:从本地 AI 用量追踪器到全平台观测工具

CodeBurn 版本演进与技术架构解析:从本地 AI 用量追踪器到全平台观测工具

CodeBurn 版本演进与技术架构解析&#xff1a;从本地 AI 用量追踪器到全平台观测工具 【免费下载链接】codeburn Free, local tool to track AI coding token usage and cost across 37 tools and agents (Claude Code, Cursor, Codex, Gemini and more), by model, project, a…

2026/9/23 9:50:27 阅读更多 →
Salt macOS keychain 模块实战指南:用 Salt 管理 macOS 钥匙串中的证书

Salt macOS keychain 模块实战指南:用 Salt 管理 macOS 钥匙串中的证书

Salt macOS keychain 模块实战指南&#xff1a;用 Salt 管理 macOS 钥匙串中的证书 【免费下载链接】salt Software to automate the management and configuration of infrastructure and applications at scale. 项目地址: https://gitcode.com/gh_mirrors/sa/salt Sa…

2026/9/23 9:49:25 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事&#xff1a;用Flutter给OpenHarmony做一款游戏集合类的App&#xff0c;说白了就是把若干小游戏塞进一个壳里&#xff0c;用统一入口分发。这个方向本身不算新鲜&#xff0c;真正让我花了不少心思的&#xff0c;是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档&#xff0c;最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事&#xff1a;今天在表后面多加了两个空白行&#xff0c;明天给客户交稿前发现整个章节的编号全部错位&#xff0c;光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年&#xff0c;说实话&#xff0c;第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年&#xff0c;流量惨淡、功能臃肿、代码自己都懒得看第二遍之后&#xff0c;我才慢慢琢磨明白一个道理&#xff1a;第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践&#xff1a;原型怎样变成可用功能分类&#xff1a;[AI/大模型]细分主题&#xff1a;AI 增强型 CI/CD 流水线自动化与 GitOps 实践&#xff1a;Agent 工作流、工具调用与任务拆解&#xff1a;从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战&#xff1a;复盘记录怎样真正派上用场分类&#xff1a;[工程技术]细分主题&#xff1a;Kubernetes 生产环境运维与排障实战&#xff1a;可复制的项目复盘模板与决策记录大部分团队的事故复盘报告&#xff0c;最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理&#xff1a;核心链路应该先拆哪一步分类&#xff1a;[工程技术]细分主题&#xff1a;Docker 容器化技术与镜像安全管理&#xff1a;核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用&#xff08;包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →