Linux LED驱动(非设备树)

1. 虚拟字符设备驱动开发基础

  1. 实现开关读写4个系统调用函数
    使用copy_to_user()函数实现用户空间和内存空间数据交互,从而实现文件读函数;使用copy_from_user()实现用户空间和内存空间数据交互,从而实现文件写函数。
  2. 初始化设备操作函数结构体
  3. 实现设备注册和注销函数
    使用register_chrdev()函数和module_init()函数注册设备,实现insmod;使用unregister_chrdev()和module_exit()函数注销设备,实现rmmod。
  4. 添加LICENSE和作者
    使用MODULE_LICENSE()函数和MODULE_AUTHOR()函数添加LICENSE和作者。
  • Linux设备

Linux驱动加载成功以后会在devices目录(\dev)下生成一个文件,应用程序通过对这个文件进行相应的操作即可实现对硬件的操作。

  • 用户空间和内核空间

对 32 位操作系统而言,它的寻址空间(虚拟地址空间)为 4G(2的32次方)。
操作系统将虚拟地址空间划分为两部分,一部分为内核空间,另一部分为用户空间。
针对 Linux 操作系统而言,最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,称为内核空间;而较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用,称为用户空间。

  • Linux字符设备驱动

在Linux内核文件include/linux/fs.h中有个叫做file_operations的结构体,此结构体就是Linux内核驱动操作函数集合。在字符设备驱动开发中最主要的工作就是实现结构体里的这些函数。
Linux驱动的两种运行方式:

  1. 将驱动编译进Linux内核中。
  2. 将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“insmod”命令加载驱动模块。
  • 常用函数:
// 开/关
open();
release();
// 读/写
write();
read();
// 注册/注销 insmod/rmmod
register_chrdev();
unregister_chrdev();
// license & author
MODULE_LICENSE();
MODULE_AUTHOR();

module_init函数用来向Linux内核注册一个模块加载函数,当使用insmod命令时,xxx_init函数就会被调用。
module_exit()函数用来向Linux内核注册一个模块卸载函数,当使用rmmod命令时,xxx_exit函数就会被调用。
atoi()函数(ASCII to integer的缩写)用于将一个字符串转换为对应的整数。

  • 设备号

设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备,高12位为主设备号,主设备号范围为0~4095,低20位为次设备号。

  • 动态设备号申请
    alloc_chardev_region();
  • 动态设备号注销
    unregister_chrdev_region();

虚拟字符设备驱动:

#include 
#include 
#include 
#include 
#include 
#include 

#define CHRDEVBASE_MAJOR	200					// 主设备号
#define CHRDEVBASE_NAME		"chrdevbase"		// 设备名

static char readbuf[100];		// 读缓冲区
static char writebuf[100];		// 写缓冲区
static char kerneldata[] = {"kernel data!"};

// 1. **实现开关读写4个系统调用函数**
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;

	/* 向用户空间发送数据 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0){
		printk("kernel senddata ok!\r\n");
	}else{
		printk("kernel senddata failed!\r\n");
	}

	//printk("chrdevbase read!\r\n");
	return 0;
}

static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel recevdata:%s\r\n", writebuf);
	}else{
		printk("kernel recevdata failed!\r\n");
	}

	//printk("chrdevbase write!\r\n");
	return 0;
}

// 2. **初始化设备操作函数结构体**
static struct file_operations chrdevbase_fops = {
	.owner		= THIS_MODULE,
	.open		= chrdevbase_open,
	.read		= chrdevbase_read,
	.write		= chrdevbase_write,
	.release	= chrdevbase_release,
};

// 3. **实现设备注册和注销函数**
static int __init chrdevbase_init(void)
{
	int retvalue = 0;
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase_init()\r\n");
	return 0;
}

static void __exit chrdevbase_exit(void)
{
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase_exit()\r\n");
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

// 4. **添加LICENSE和作者**
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");

应用程序:

  1. 打开文件
  2. 从文件读取数据
  3. 将数据写入文件
  4. 关闭文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 

static char usrdata[] = {"usr data!"};

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];

	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开驱动文件 */
	fd  = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0){
			printf("read file %s failed!\r\n", filename);
		}else{
			/*  读取成功,打印出读取成功的数据 */
			printf("read data:%s\r\n",readbuf);
		}
	}

	if(atoi(argv[2]) == 2){
		/* 向设备驱动写数据 */
		memcpy(writebuf, usrdata, sizeof(usrdata));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0){
			printf("write file %s failed!\r\n", filename);
		}
	}

	/* 关闭设备 */
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s\r\n", filename);
		return -1;
	}

	return 0;
}

2. 字符设备驱动下的LED驱动

  1. 实现开关读写4个系统调用函数
    使用copy_from_user()实现用户空间和内存空间数据交互,并使用readl()函数读取寄存器映射后的虚拟地址的数据,使用writel()函数根据寄存器映射后的虚拟地址将数据写入寄存器,从而实现文件写函数(LED的亮灭)。
  2. 初始化设备操作函数结构体
  3. 实现设备注册和注销函数
    使用ioremap()函数获取物理地址空间对应的虚拟地址空间,然后使能GPIO、关闭中断、设置方向,并使用register_chrdev()函数和module_init()函数注册设备;使用iounmap()函数取消内存映射,并使用unregister_chrdev()和module_exit()函数注销设备。
  4. 添加LICENSE和作者
    使用MODULE_LICENSE()函数和MODULE_AUTHOR()函数添加LICENSE和作者。
  • ioremap()

用于获取指定物理地址空间对应的虚拟地址空间。

  • 将物理地址0x10001000对应的32位寄存器映射到虚拟地址addr上:
    c addr = ioremap(0x10001000, 4);
  • iounmap()

用于释放掉映射的虚拟地址空间。
释放掉ioremap函数所做的映射:
iounmap(addr);

  • readb()/readw()/readl()
  • writeb()/writew()/writel()
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define LED_MAJOR		200			/* 主设备号 */
#define LED_NAME		"led"		/* 设备名字 */

/* 
 * GPIO相关寄存器地址定义
 */
#define ZYNQ_GPIO_REG_BASE			0xE000A000
#define DATA_OFFSET					0x00000040
#define DIRM_OFFSET					0x00000204
#define OUTEN_OFFSET				0x00000208
#define INTDIS_OFFSET				0x00000214
#define APER_CLK_CTRL				0xF800012C

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;

// 1. **实现开关读写4个系统调用函数**
static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *offt)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{
	int ret;
	int val;
	char kern_buf[1];

	ret = copy_from_user(kern_buf, buf, cnt);	// 得到应用层传递过来的数据
	if(0 > ret) {
		printk(KERN_ERR "kernel write failed!\r\n");
		return -EFAULT;
	}

	val = readl(data_addr);
	if (0 == kern_buf[0])
		val &= ~(0x1U << 7);		// 如果传递过来的数据是0则关闭led
	else if (1 == kern_buf[0])
		val |= (0x1U << 7);			// 如果传递过来的数据是1则点亮led

	writel(val, data_addr);
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

// 2. **初始化设备操作函数结构体**
static struct file_operations led_fops = {
	.owner		= THIS_MODULE,
	.open		= led_open,
	.read		= led_read,
	.write		= led_write,
	.release	= led_release,
};

// 3. **实现设备注册和注销函数**
static int __init led_init(void)
{
	u32 val;
	int ret;

	/* 1.寄存器地址映射 */
	data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);
	dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);
	outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);
	intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);
	aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);

	/* 2.使能GPIO时钟 */
	val = readl(aper_clk_ctrl_addr);
	val |= (0x1U << 22);
	writel(val, aper_clk_ctrl_addr);

	/* 3.关闭中断功能 */
	val |= (0x1U << 7);
	writel(val, intdis_addr);

	/* 4.设置GPIO为输出功能 */
	val = readl(dirm_addr);
	val |= (0x1U << 7);
	writel(val, dirm_addr);

	/* 5.使能GPIO输出功能 */
	val = readl(outen_addr);
	val |= (0x1U << 7);
	writel(val, outen_addr);

	/* 6.默认关闭LED */
	val = readl(data_addr);
	val &= ~(0x1U << 7);
	writel(val, data_addr);

	/* 7.注册字符设备驱动 */
	ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
	if(0 > ret){
		printk(KERN_ERR "Register LED driver failed!\r\n");
		return ret;
	}

	return 0;
}

static void __exit led_exit(void)
{
	/* 1.卸载设备 */
	unregister_chrdev(LED_MAJOR, LED_NAME);

	/* 2.取消内存映射 */
	iounmap(data_addr);
	iounmap(dirm_addr);
	iounmap(outen_addr);
	iounmap(intdis_addr);
	iounmap(aper_clk_ctrl_addr);
}

module_init(led_init);
module_exit(led_exit);

// 4. **添加LICENSE和作者**
MODULE_AUTHOR("DengTao <[email protected]>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

应用程序:

  1. 打开文件
  2. 将字符串转为数据,并将数据写入文件(开关LED)
  3. 关闭文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	unsigned char buf[1];

	if(3 != argc) {
		printf("Usage:\n"
		"\t./ledApp /dev/led 1		@ close LED\n"
		"\t./ledApp /dev/led 0		@ open LED\n"
		);
		return -1;
	}

	/* 打开设备 */
	fd = open(argv[1], O_RDWR);
	if(0 > fd) {
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	/* 将字符串转换为int型数据 */
	buf[0] = atoi(argv[2]);

	/* 向驱动写入数据 */
	ret = write(fd, buf, sizeof(buf));
	if(0 > ret){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	/* 关闭设备 */
	close(fd);
	return 0;
}

3. 新字符设备驱动开发基础

  • struct cdev test_cdev;

在Linux里使用cdev结构体表示一个字符设备,此结构体中就包含file_operations和设备号。

  • cdev_init()

使用cdev_init()函数对次结构体进行初始化。

  • cdev_add()

首先使用cdev_init()函数完成对cdev结构体变量的初始化,然后使用cdev_add()函数向Linux系统添加这个字符设备。

  • cdev_del()

卸载驱动的时候使用cdev_del函数从Linux内核中删除相应的字符设备。

  • udev

使用udev自动创建节点。
一般在cdev_add函数后面添加自动创建设备节点相关代码,使用class_create()函数创建一个class类,再使用device_create()函数在类下面创建设备;卸载驱动程序时先使用device_destroy()函数删除设备,再使用class_destroy()删除掉类。

  • 文件私有数据

每个硬件设备都有一些属性,比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候可以将这些属性组成一个结构体,并在编写驱动open函数的时候将设备结构体作为私有数据添加到设备文件中。

  • register_chrdev_region()

分配设备编号,注册设备与注销设备的函数均在fs.h中申明,如下:
extern int register_chrdev_region(dev_t, unsigned, const char ); //静态的申请和注册设备号
extern int alloc_chrdev_region(dev_t, unsigned, const char
);//动态的申请注册一个设备号
extern int register_chrdev(unsigned int, const char *,struct file_operations *);//int为0时候动态注册,非零时候静态注册。
extern int unregister_chrdev(unsigned int, const char *);
extern void unregister_chrdev_region(dev_t, unsigned);

4. 新字符设备驱动下的LED驱动

  1. 定义设备结构体
    设备结构体包含cdev、类、设备、设备号等。
  2. 实现开关读写4个系统调用函数
    在文件打开函数中使用filp->private_data设置私有数据;使用copy_from_user()实现用户空间和内存空间数据交互,并使用readl()函数读取寄存器映射后的虚拟地址的数据,使用writel()函数根据寄存器映射后的虚拟地址将数据写入寄存器,从而实现文件写函数(LED的亮灭)。
  3. 初始化设备操作函数结构体
  4. 实现设备注册和注销函数
    使用ioremap()函数获取物理地址空间对应的虚拟地址空间,然后使能GPIO、关闭中断、设置方向,使用register_chrdev_region()函数、alloc_chrdev_region()函数创建设备号,并使用module_init()函数指定设别注册函数实现insmod,使用cdev_init()初始化cdev,使用cdev_add()添加cdev,使用class_create()创建类,使用device_create()创建设备;使用device_destroy()注销设备,使用class_destroy()注销类,使用cdev_del()删除cdev,使用unregister_chrdev_region()注销设备号,使用led_iounmap()取消地址映射,并使用module_exit()函数指定设备注销函数实现rmmod。
  5. 添加LICENSE和作者
    使用MODULE_LICENSE()函数和MODULE_AUTHOR()函数添加LICENSE和作者。
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define NEWCHRLED_CNT		1				/* 设备号个数 */
#define NEWCHRLED_NAME		"newchrled"		/* 名字 */

/* 
 * GPIO相关寄存器地址定义
 */
#define ZYNQ_GPIO_REG_BASE			0xE000A000
#define DATA_OFFSET					0x00000040
#define DIRM_OFFSET					0x00000204
#define OUTEN_OFFSET				0x00000208
#define INTDIS_OFFSET				0x00000214
#define APER_CLK_CTRL				0xF800012C

/* 映射后的寄存器虚拟地址指针 */
static void __iomem *data_addr;
static void __iomem *dirm_addr;
static void __iomem *outen_addr;
static void __iomem *intdis_addr;
static void __iomem *aper_clk_ctrl_addr;

// 0. **定义设备结构体**
/* newchrled设备结构体 */
struct newchrled_dev {
	dev_t devid;			/* 设备号 */
	struct cdev cdev;		/* cdev */
	struct class *class;	/* 类 */
	struct device *device;	/* 设备 */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */
};

static struct newchrled_dev newchrled;	/* led设备 */

// 1. **实现开关读写4个系统调用函数**
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &newchrled;	/* 设置私有数据 */
	return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *offt)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{
	int ret;
	int val;
	char kern_buf[1];

	ret = copy_from_user(kern_buf, buf, cnt);	// 得到应用层传递过来的数据
	if(0 > ret) {
		printk(KERN_ERR "kernel write failed!\r\n");
		return -EFAULT;
	}

	val = readl(data_addr);
	if (0 == kern_buf[0])
		val &= ~(0x1U << 7);		// 如果传递过来的数据是0则关闭led
	else if (1 == kern_buf[0])
		val |= (0x1U << 7);			// 如果传递过来的数据是1则点亮led

	writel(val, data_addr);
	return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static inline void led_ioremap(void)
{
	data_addr = ioremap(ZYNQ_GPIO_REG_BASE + DATA_OFFSET, 4);
	dirm_addr = ioremap(ZYNQ_GPIO_REG_BASE + DIRM_OFFSET, 4);
	outen_addr = ioremap(ZYNQ_GPIO_REG_BASE + OUTEN_OFFSET, 4);
	intdis_addr = ioremap(ZYNQ_GPIO_REG_BASE + INTDIS_OFFSET, 4);
	aper_clk_ctrl_addr = ioremap(APER_CLK_CTRL, 4);
}

static inline void led_iounmap(void)
{
	iounmap(data_addr);
	iounmap(dirm_addr);
	iounmap(outen_addr);
	iounmap(intdis_addr);
	iounmap(aper_clk_ctrl_addr);
}

// 2. **初始化设备操作函数结构体**
static struct file_operations newchrled_fops = {
	.owner		= THIS_MODULE,
	.open		= led_open,
	.read		= led_read,
	.write		= led_write,
	.release	= led_release,
};

// 3. **实现设备注册和注销函数**
static int __init led_init(void)
{
	u32 val;
	int ret;

	/* 1.寄存器地址映射 */
	led_ioremap();

	/* 2.使能GPIO时钟 */
	val = readl(aper_clk_ctrl_addr);
	val |= (0x1U << 22);
	writel(val, aper_clk_ctrl_addr);

	/* 3.关闭中断功能 */
	val |= (0x1U << 7);
	writel(val, intdis_addr);

	/* 4.设置GPIO为输出功能 */
	val = readl(dirm_addr);
	val |= (0x1U << 7);
	writel(val, dirm_addr);

	/* 5.使能GPIO输出功能 */
	val = readl(outen_addr);
	val |= (0x1U << 7);
	writel(val, outen_addr);

	/* 6.默认关闭LED */
	val = readl(data_addr);
	val &= ~(0x1U << 7);
	writel(val, data_addr);

	/* 7.注册字符设备驱动 */
	 /* 创建设备号 */
	if (newchrled.major) {
		newchrled.devid = MKDEV(newchrled.major, 0);
		ret = register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
		if (ret)
			goto out1;
	} else {
		ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
		if (ret)
			goto out1;

		newchrled.major = MAJOR(newchrled.devid);
		newchrled.minor = MINOR(newchrled.devid);
	}

	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor); 

	 /* 初始化cdev */
	newchrled.cdev.owner = THIS_MODULE;
	cdev_init(&newchrled.cdev, &newchrled_fops);

	 /* 添加一个cdev */
	ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
	if (ret)
		goto out2;

	 /* 创建类 */
	newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.class)) {
		ret = PTR_ERR(newchrled.class);
		goto out3;
	}

	 /* 创建设备 */
	newchrled.device = device_create(newchrled.class, NULL,
				newchrled.devid, NULL, NEWCHRLED_NAME);
	if (IS_ERR(newchrled.device)) {
		ret = PTR_ERR(newchrled.device);
		goto out4;
	}

	return 0;

out4:
	class_destroy(newchrled.class);

out3:
	cdev_del(&newchrled.cdev);

out2:
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

out1:
	led_iounmap();

	return ret;
}

static void __exit led_exit(void)
{
	/* 注销设备 */
	device_destroy(newchrled.class, newchrled.devid);

	/* 注销类 */
	class_destroy(newchrled.class);

	/* 删除cdev */
	cdev_del(&newchrled.cdev);

	/* 注销设备号 */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);

	/* 取消地址映射 */
	led_iounmap();
}

/* 驱动模块入口和出口函数注册 */
module_init(led_init);
module_exit(led_exit);

MODULE_AUTHOR("DengTao <[email protected]>");
MODULE_DESCRIPTION("Alientek ZYNQ GPIO LED Driver");
MODULE_LICENSE("GPL");

应用程序:

  1. 打开文件
  2. 将字符串转为数据,并将数据写入文件(开关LED)
  3. 关闭文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
 * @description		: main主程序
 * @param - argc	: argv数组元素个数
 * @param - argv	: 具体参数
 * @return			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, ret;
	unsigned char buf[1];

	if(3 != argc) {
		printf("Usage:\n"
		"\t./ledApp /dev/led 1		@ close LED\n"
		"\t./ledApp /dev/led 0		@ open LED\n"
		);
		return -1;
	}

	/* 打开设备 */
	fd = open(argv[1], O_RDWR);
	if(0 > fd) {
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	/* 将字符串转换为int型数据 */
	buf[0] = atoi(argv[2]);

	/* 向驱动写入数据 */
	ret = write(fd, buf, sizeof(buf));
	if(0 > ret){
		printf("LED Control Failed!\r\n");
		close(fd);
		return -1;
	}

	/* 关闭设备 */
	close(fd);
	return 0;
}

你可能感兴趣的:(linux,arm)