Linux驱动开发笔记(二十六)——PWM(SG90驱动)

资料:《【正点原子】I.MX6U开发指南V1.81.pdf》七十三章

6. PWM子系统 --- [野火]嵌入式Linux驱动开发实战指南


一、相关结构体

以下代码均来自include/linux/pwm.h

1.1 pwm_chip

cpp 复制代码
/**
 * struct pwm_chip - abstract a PWM controller
 * @dev: device providing the PWMs
 * @list: list node for internal use
 * @ops: callbacks for this PWM controller    PWM外设的各种操作函数集合
 * @base: number of first PWM controlled by this chip 芯片控制的第一个 PWM 的编号
 * @npwm: number of PWMs controlled by this chip 此芯片的PWM控制器数量
 * @pwms: array of PWM devices allocated by the framework 
 * @can_sleep: must be true if the .config(), .enable() or .disable()
 *             operations may sleep
 */
struct pwm_chip {
	struct device		*dev;
	struct list_head	list;
	const struct pwm_ops	*ops;
	int			base;
	unsigned int		npwm;
	struct pwm_device	*pwms;
	struct pwm_device *	(*of_xlate)(struct pwm_chip *pc, const struct of_phandle_args *args);
	unsigned int		of_pwm_n_cells;
	bool			    can_sleep;
};

1.2 pwm_ops

其中,struct pwm_ops *ops就是PWM设备的操作集,写代码时需要由我们来实现。其结构体定义如下:

cpp 复制代码
/**
 * struct pwm_ops - PWM controller operations
 * @request: optional hook for requesting a PWM    申请
 * @free: optional hook for freeing a PWM          释放
 * @config: configure duty cycles and period length for this PWM   配置 PWM 周期和占空比
 * @set_polarity: configure the polarity of this PWM  设置 PWM 极性
 * @enable: enable PWM output toggling             使能
 * @disable: disable PWM output toggling           关闭
 * @dbg_show: optional routine to show contents in debugfs
 * @owner: helps prevent removal of modules exporting active PWMs
 */
struct pwm_ops {
	int			(*request)(struct pwm_chip *chip,
					   struct pwm_device *pwm);
	void			(*free)(struct pwm_chip *chip,
					struct pwm_device *pwm);
	int			(*config)(struct pwm_chip *chip,
					  struct pwm_device *pwm,
					  int duty_ns, int period_ns);
	int			(*set_polarity)(struct pwm_chip *chip,
					  struct pwm_device *pwm,
					  enum pwm_polarity polarity);
	int			(*enable)(struct pwm_chip *chip,
					  struct pwm_device *pwm);
	void			(*disable)(struct pwm_chip *chip,
					   struct pwm_device *pwm);
#ifdef CONFIG_DEBUG_FS
	void			(*dbg_show)(struct pwm_chip *chip,
					    struct seq_file *s);
#endif
	struct module		*owner;
};

1.3 pwm_device

cpp 复制代码
struct pwm_device {    
	const char		*label;       // pwm设备名
	unsigned long		flags;    // 标志位
	unsigned int		hwpwm;    //
	unsigned int		pwm;      // pwm设备索引
	struct pwm_chip		*chip;    // pwm控制器
	void			    *chip_data;

	unsigned int		period; 	// pwm周期,单位ns
	unsigned int		duty_cycle;	// 占空比,单位ns
                                    // 如果是正常输出极性,表示一个周期内高电平持续时间
                                    // 如果设置非输出反相,则是一个周期内低电平持续时间
	enum pwm_polarity	polarity;   // 枚举,输出类型,具体如下↓
};

enum pwm_polarity {
	PWM_POLARITY_NORMAL,    // 正常
	PWM_POLARITY_INVERSED,  // 反相
};

二、相关函数

drivers/pwm/core.c

2.1 注册 / 注销pwm_chip

用于向内核注册一个新的pwm_chip。

cpp 复制代码
int pwmchip_add(struct pwm_chip *chip)  
// chip:要添加的pwm_chip
// return:0则成功,负值失败

// 如果chip->base < 0,会动态分配一个pwm编号

注销:

cpp 复制代码
/**
 * pwmchip_remove() - remove a PWM chip    移除一个pwm_chip
 * @chip: the PWM chip to remove     要移除的pwm_chip
 *
 * Removes a PWM chip. This function may return busy if the PWM chip provides
 * a PWM device that is still requested.
 * 如果pwm_chip提供的PWM设备仍被请求,此函数可能会return BUSY。
 * return 0则成功,负值失败
 */
int pwmchip_remove(struct pwm_chip *chip)

2.2 申请 / 释放 pwm_device

申请:

cpp 复制代码
// 根据dev找pwm
struct pwm_device *pwm_get(struct device *dev, const char *con_id)
struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id)
// dev:当前这个驱动对应的设备对象,对应pwm-names,可以传NULL
// con_id:消费者名/连接名


// OF函数,通过设备树节点找pwm
struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np,                          const char *con_id)
// np:设备树节点,使用OF函数获取
// con_id:消费者名/连接名,对应pwm-names,可以传NULL
// dev:devm版会有这个参数,因为自动释放 需要和设备生命周期绑定

释放:

cpp 复制代码
void pwm_put(struct pwm_device *pwm)
void devm_pwm_put(struct device *dev, struct pwm_device *pwm)
// pwm:要释放的pwm device
// dev:devm版会有这个参数,因为自动释放 需要和设备生命周期绑定

2.3 使能 / 停用

配置频率和占空比:

cpp 复制代码
/**
 * pwm_config() - change a PWM device configuration
 * @pwm: PWM device
 * @duty_ns: "on" time (in nanoseconds) 高电平时间ns(正常)   低电平时间ns(反相)
 * @period_ns: duration (in nanoseconds) of one cycle  周期ns
 */
int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
//return:0成功,负数:错误码

用于设置PWM极性:

cpp 复制代码
/**
 * pwm_set_polarity() - configure the polarity of a PWM signal
 * @pwm: PWM device
 * @polarity: new polarity of the PWM signal 极性,具体类型详见1.3
 *
 * Note that the polarity cannot be configured while the PWM device is enabled
 * 如果PWM device已经被使能,不能修改极性
 */
int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity)
//return:0成功,负数:错误码

使能:

cpp 复制代码
/**
 * pwm_enable() - start a PWM output toggling
 * @pwm: PWM device
 */
int pwm_enable(struct pwm_device *pwm)
//return:0成功,负数:错误码

关闭:

cpp 复制代码
/**
 * pwm_disable() - stop a PWM output toggling
 * @pwm: PWM device
 */
void pwm_disable(struct pwm_device *pwm)

三、代码

3.1 设备树

IMX6ULL有8路PWM输出,都属于AIPS1域。在imx6ull.dtsi文件中的aips1中可以找到pwm1~8节点。依据《指南pdf》,本次实验使用PWM3,对应GPIO1_IO04。底板原理图中没有写,但在【正点原子】阿尔法Linux开发板(A盘)-基础资料/07、I.MX6U参考资料/02、I.MX6ULL芯片资料/IMX6ULL参考手册.pdf中的2475页有写:

imx6ull-alientek-emmc.dts文件中,在&iomuxc的imx6ul-evk下新增下面的代码,添加gpio1 io04引脚:

cpp 复制代码
		pinctrl_pwm3: pwm3grp {
			fsl,pins = <MX6UL_PAD_GPIO1_IO04__PWM3_OUT 0x110b0>;
		};

imx6ull-alientek-emmc.dts增加以下代码,向imx6ull.dtsi的pwm节点追加控制器信息。直接写到最外面:

cpp 复制代码
	&pwm3 {
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_pwm3>;
		status = "okay";
	};

最后在根目录下新增sg90的设备节点:

cpp 复制代码
	sg90{
		compatible = "sg90";
		pwms = <&pwm3 0 20000000>;// pwm1  id=0  周期20000000ns->20ms,这个20ms是sg90统一的周期
        status = "okay";
	};

然后检查一下有没有其他地方使用了pwm3、gpio1 io04,有的话注释掉。比如这个↓

编译,然后将生成的文件复制到tftproot下,然后重启开发板:

cpp 复制代码
cd /...../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
make dtbs
cp arch/arm/boot/dts/imx6ull-alientek-emmc.dts /...../tftpboot/

3.2 文件结构

bash 复制代码
PWM (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── pwm.code-workspace
├── Makefile
├── pwm.c
└── pwmAPP.c

3.3 Makefile

bash 复制代码
CFLAGS_MODULE += -w

KERNELDIR := /....../linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek  # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径

CURRENT_PATH := $(shell pwd)	# 当前路径

obj-m := pwm.o			    # 编译文件

build: kernel_modules			# 编译模块

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

3.4 pwm.c驱动代码

3.4.1 设备结构体

cpp 复制代码
#define PLATFORM_NAME "sg90" // 设备名
#define PLATFORM_COUNT 1     // 设备数量

/////////////////////////////////////////////////////

/* 设备结构体 */
struct sg90_struct{
    dev_t devid;            /* 设备号 */
    int major;              /* 主设备号 */
    int minor;              /* 此设备号 */
    struct cdev cdev;
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    struct device_node *nd;
    struct pwm_device *pwm_dev;
};
struct sg90_struct sg90dev;

3.4.2 file_operations

cpp 复制代码
static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
    int ret,angle,duty_ns;
    char databuf[32];
    size_t len = min(count, sizeof(databuf)-1);
    struct sg90_struct *dev = filp->private_data;

    memset(databuf, 0, sizeof(databuf));
    if(copy_from_user(databuf, buf, len))return -EFAULT;
    databuf[len]='\0';
    
    ret = kstrtoint(databuf, 10, &angle); // string -> int
    if(ret)return -EINVAL;
    if(angle < 0) angle = 0;
    else if(angle > 180) angle = 180;

    duty_ns = 500000 + (2500000 - 500000) * angle / 180;
    ret = pwm_config(dev->pwm_dev, duty_ns, 20000000);
    if(ret < 0){
        pr_err("fild_pwm_config!\r\n");
        return ret;
    }

    return len;
}

static int sg90_open(struct inode *inode, struct file *filp){
    filp->private_data = &sg90dev;
    return 0;
}
static int sg90_release(struct inode *inode, struct file *filp){
    return 0;
}
/* 字符设备操作集合 */
static const struct file_operations sg90_fops = {
    .owner = THIS_MODULE,
    .write = sg90_write,
    .open = sg90_open,
    .release = sg90_release,
};

3.4.3 platform结构体 + 匹配表

cpp 复制代码
static const struct of_device_id sg90_of_match[] = {
    {.compatible = "sg90"}, // 要和新增的设备数节点中的compatible匹配
    { /* sentinel */ }, // 哨兵元素
};
 
// platform驱动结构体
static struct platform_driver sg90_driver = {
    .driver = {
        .name = "sg90", // 无设备树时使用name与设备进行匹配
        .of_match_table = sg90_of_match, // 有设备树时进行匹配
    },
    .probe = sg90_probe,
    .remove = sg90_remove,
};

3.4.4 probe + remove

cpp 复制代码
static int sg90_probe(struct platform_device *dev){
    pr_info("sg90 probe\r\n");
    int ret = 0;

    /* 1.注册字符设备驱动 */
    sg90dev.devid = 0;
    ret = alloc_chrdev_region(&sg90dev.devid, 0, PLATFORM_COUNT, PLATFORM_NAME);
    if(ret<0){
        pr_err("fail_chrdev!\n");
        goto fail_chrdev;
    }
    sg90dev.major = MAJOR(sg90dev.devid);
    sg90dev.minor = MINOR(sg90dev.devid);

    /* 2.初始化cdev */
    sg90dev.cdev.owner = THIS_MODULE;  
    cdev_init(&sg90dev.cdev, &sg90_fops);
    /* 3.添加cdev */
    ret = cdev_add(&sg90dev.cdev, sg90dev.devid, PLATFORM_COUNT);
    if(ret<0){
        pr_err("fail_add_cdev!\n");
        goto fail_add_cdev;
    }
    /* 4.创建类 */
    sg90dev.class = class_create(THIS_MODULE, PLATFORM_NAME);
    if(IS_ERR(sg90dev.class)){
        pr_err("fail_create_class!\n");
        ret = PTR_ERR(sg90dev.class);
        goto fail_create_class;
    }
    /* 5.创建设备 */
    sg90dev.device = device_create(sg90dev.class, NULL, sg90dev.devid, NULL, PLATFORM_NAME);
    if(IS_ERR(sg90dev.device)){
        pr_err("fail_create_device!\n");
        ret = PTR_ERR(sg90dev.device);
        goto fail_create_device;
    }
    sg90dev.nd = dev->dev.of_node; 
    // platform_device类型中的device结构体成员中已经包括了of_node(device_node类型)
    // 可以直接读取,不需要再手动寻找of_find_node_by_path
    struct device_node *node = dev->dev.of_node;  // 设备树

    sg90dev.pwm_dev = devm_of_pwm_get(&dev->dev, node, NULL);
    if (IS_ERR(sg90dev.pwm_dev)){
        pr_err("fail_get_pwm!\n");
        ret = PTR_ERR(sg90dev.pwm_dev);
        goto fail_pwm;
    }
    ret = pwm_config(sg90dev.pwm_dev, 500000, 20000000); // 周期20ms,高电平时间0.5ms,舵机角度0
    if(ret < 0){
        pr_err("fail_config_pwm!\n");
        goto fail_pwm;
    }
    ret = pwm_set_polarity(sg90dev.pwm_dev, PWM_POLARITY_NORMAL); // 正常极性
    if(ret < 0){
        pr_err("fail_set_polarity!\n");
        goto fail_pwm;
    }
    ret = pwm_enable(sg90dev.pwm_dev);
    if(ret < 0){
        pr_err("fail_enable_pwm!\n");
        goto fail_pwm;
    }  
    return 0;

fail_pwm:
    device_destroy(sg90dev.class,sg90dev.devid);
fail_create_device:
    class_destroy(sg90dev.class);
fail_create_class:
    cdev_del(&sg90dev.cdev);
fail_add_cdev:
    unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
fail_chrdev:
    return ret;
}
 
 
static int sg90_remove(struct platform_device * dev){
    pr_info("sg90 remove\r\n");
 
    pwm_disable(sg90dev.pwm_dev);
    device_destroy(sg90dev.class, sg90dev.devid);
    class_destroy(sg90dev.class);
    cdev_del(&sg90dev.cdev);
    unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
    return 0;
}

这里有一个问题,在配置极性的时候回报错:

fail_set_polarity!

sg90: probe of servo failed with error -38

可能是控制器不支持这个功能?先注释掉了

3.4.5 驱动出入口

cpp 复制代码
// 加载驱动
static int __init sg90driver_init(void){
    return platform_driver_register(&sg90_driver);
}
 
// 卸载驱动
static void __exit sg90driver_exit(void){
    platform_driver_unregister(&sg90_driver);
}

module_init(sg90driver_init);
module_exit(sg90driver_exit);
MODULE_LICENSE("GPL");

3.4.6 完整驱动代码

cpp 复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mod_devicetable.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/pwm.h>


#define PLATFORM_NAME "sg90" // 设备名
#define PLATFORM_COUNT 1     // 设备数量

/////////////////////////////////////////////////////

/* 设备结构体 */
struct sg90_struct{
    dev_t devid;            /* 设备号 */
    int major;              /* 主设备号 */
    int minor;              /* 此设备号 */
    struct cdev cdev;
    struct class *class;    /* 类 */
    struct device *device;  /* 设备 */
    struct device_node *nd;
    struct pwm_device *pwm_dev;
};
struct sg90_struct sg90dev;

/////////////////////////////////////////////////////
static ssize_t sg90_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
    int ret,angle,duty_ns;
    char databuf[32];
    size_t len = min(count, sizeof(databuf)-1);
    struct sg90_struct *dev = filp->private_data;

    memset(databuf, 0, sizeof(databuf));
    if(copy_from_user(databuf, buf, len))return -EFAULT;
    databuf[len]='\0';
    
    ret = kstrtoint(databuf, 10, &angle); // string -> int
    if(ret)return -EINVAL;
    if(angle < 0) angle = 0;
    else if(angle > 180) angle = 180;

    duty_ns = 500000 + (2500000 - 500000) * angle / 180;
    ret = pwm_config(dev->pwm_dev, duty_ns, 20000000);
    if(ret < 0){
        pr_err("fild_pwm_config!\r\n");
        return ret;
    }

    return len;
}

static int sg90_open(struct inode *inode, struct file *filp){
    filp->private_data = &sg90dev;
    return 0;
}
static int sg90_release(struct inode *inode, struct file *filp){
    return 0;
}
/* 字符设备操作集合 */
static const struct file_operations sg90_fops = {
    .owner = THIS_MODULE,
    .write = sg90_write,
    .open = sg90_open,
    .release = sg90_release,
};
 
///////////////////////////////////////////////////////
static int sg90_probe(struct platform_device *dev){
    pr_info("sg90 probe\r\n");
    int ret = 0;

    /* 1.注册字符设备驱动 */
    sg90dev.devid = 0;
    ret = alloc_chrdev_region(&sg90dev.devid, 0, PLATFORM_COUNT, PLATFORM_NAME);
    if(ret<0){
        pr_err("fail_chrdev!\n");
        goto fail_chrdev;
    }
    sg90dev.major = MAJOR(sg90dev.devid);
    sg90dev.minor = MINOR(sg90dev.devid);

    /* 2.初始化cdev */
    sg90dev.cdev.owner = THIS_MODULE;  
    cdev_init(&sg90dev.cdev, &sg90_fops);
    /* 3.添加cdev */
    ret = cdev_add(&sg90dev.cdev, sg90dev.devid, PLATFORM_COUNT);
    if(ret<0){
        pr_err("fail_add_cdev!\n");
        goto fail_add_cdev;
    }
    /* 4.创建类 */
    sg90dev.class = class_create(THIS_MODULE, PLATFORM_NAME);
    if(IS_ERR(sg90dev.class)){
        pr_err("fail_create_class!\n");
        ret = PTR_ERR(sg90dev.class);
        goto fail_create_class;
    }
    /* 5.创建设备 */
    sg90dev.device = device_create(sg90dev.class, NULL, sg90dev.devid, NULL, PLATFORM_NAME);
    if(IS_ERR(sg90dev.device)){
        pr_err("fail_create_device!\n");
        ret = PTR_ERR(sg90dev.device);
        goto fail_create_device;
    }
    sg90dev.nd = dev->dev.of_node; 
    // platform_device类型中的device结构体成员中已经包括了of_node(device_node类型)
    // 可以直接读取,不需要再手动寻找of_find_node_by_path
    struct device_node *node = dev->dev.of_node;  // 设备树

    sg90dev.pwm_dev = devm_of_pwm_get(&dev->dev, node, NULL);
    if (IS_ERR(sg90dev.pwm_dev)){
        pr_err("fail_get_pwm!\n");
        ret = PTR_ERR(sg90dev.pwm_dev);
        goto fail_pwm;
    }
    ret = pwm_config(sg90dev.pwm_dev, 500000, 20000000); // 周期20ms,高电平时间0.5ms,舵机角度0
    if(ret < 0){
        pr_err("fail_config_pwm!\n");
        goto fail_pwm;
    }
    // ret = pwm_set_polarity(sg90dev.pwm_dev, PWM_POLARITY_NORMAL); // 正常极性
    // if(ret < 0){
    //     pr_err("fail_set_polarity!\n");
    //     goto fail_pwm;
    // }
    ret = pwm_enable(sg90dev.pwm_dev);
    if(ret < 0){
        pr_err("fail_enable_pwm!\n");
        goto fail_pwm;
    }  
    return 0;

fail_pwm:
    device_destroy(sg90dev.class,sg90dev.devid);
fail_create_device:
    class_destroy(sg90dev.class);
fail_create_class:
    cdev_del(&sg90dev.cdev);
fail_add_cdev:
    unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
fail_chrdev:
    return ret;
}
 
 
static int sg90_remove(struct platform_device * dev){
    pr_info("sg90 remove\r\n");
 
    pwm_disable(sg90dev.pwm_dev);
    device_destroy(sg90dev.class, sg90dev.devid);
    class_destroy(sg90dev.class);
    cdev_del(&sg90dev.cdev);
    unregister_chrdev_region(sg90dev.devid, PLATFORM_COUNT);
    return 0;
}
 
static const struct of_device_id sg90_of_match[] = {
    {.compatible = "sg90"}, // 要和新增的设备数节点gpio中的compatible匹配
    { /* sentinel */ }, // 哨兵元素
    // 后面可以接更多的{.compatible = "............"}
};
 
// platform驱动结构体
static struct platform_driver sg90_driver = {
    .driver = {
        .name = "sg90", // 无设备树时使用name与设备进行匹配
        .of_match_table = sg90_of_match, // 有设备树时进行匹配
    },
    .probe = sg90_probe,
    .remove = sg90_remove,
};
 
///////////////////////////////////////////////////////////////////
 
// 加载驱动
static int __init sg90driver_init(void){
    return platform_driver_register(&sg90_driver);
}
 
// 卸载驱动
static void __exit sg90driver_exit(void){
    platform_driver_unregister(&sg90_driver);
}

module_init(sg90driver_init);
module_exit(sg90driver_exit);
MODULE_LICENSE("GPL");

3.5 应用程序测试

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]){
    int fd;
    char buf[32];

    if (argc != 2) {
        printf("Usage: %s <angle>\n", argv[0]);
        return -1;
    }

    fd = open("/dev/sg90", O_WRONLY);
    if (fd < 0) {
        printf("open /dev/sg90 failed");
        return -1;
    }

    if (write(fd, argv[1], strlen(argv[1])) < 0) {
        printf("write failed");
        close(fd);
        return -1;
    }

    printf("set angle = %d\n", atoi(argv[1]));

    close(fd);
    return 0;
}

3.6 测试

bash 复制代码
make
arm-linux-gnueabihf-gcc pwmAPP.c -o pwmAPP
sudo cp pwm.ko pwmAPP /home/for/linux/nfs/rootfs/lib/modules/4.1.15/
bash 复制代码
cd /lib/modules/4.1.15
depmod
modprobe pwm.ko
./pwmAPP XXX # 输入角度
相关推荐
赵民勇2 小时前
gtk-update-icon-cache用法技巧总结
linux
wefg12 小时前
【Linux】线程同步与互斥 - 2(线程同步/条件变量/基于阻塞/环形队列的cp模型/线程池/线程安全/读写锁)
linux·开发语言
_李小白2 小时前
【AI大模型学习笔记之平台篇】第三篇:Minimax
人工智能·笔记·学习
小生不才yz2 小时前
【Makefile 专家之路 | 基础篇】02. 初试锋芒:编写第一个 Makefile 与运行机制深度剖析
linux
【数据删除】3482 小时前
计算机复试学习笔记 Day41
笔记·学习·算法
Xu_youyaxianshen2 小时前
[特殊字符] Docker 小白极速入门笔记
linux·docker
getapi2 小时前
FinalShell 连接 CentOS 7 文件管理失败修复教程
linux·运维·centos
程序员学习随笔2 小时前
ext4 原理篇(三):日志子系统 Journal 深度剖析 —— 如何保障数据一致性?
linux·c++
OxyTheCrack2 小时前
【C++】一篇文章悲观锁与乐观锁与其思想在C++语言中的应用
linux·开发语言·数据库·c++·笔记