嵌入式驱动初级-中断
创始人
2024-02-17 05:14:31

文章目录

  • 前言
  • 一、Linux 中断 API 函数
  • 二、Linux 中断实现
  • 三、中断上半部与下半部
  • 四、下半部机制之tasklet ---- 基于软中断
  • 五、按键中断下半部机制之tasklet
  • 六、下半部机制之workqueue ----- 基于内核线程
  • 七、按键中断下半部机制之workqueue


前言

记录嵌入式驱动学习笔记

一、Linux 中断 API 函数

设备树中增加按键节点
在这里插入图片描述

mykey2_node {compatible = "mykey2,key2";key2-gpio = <&gpx1 1 0>;interrupt-parent = <&gpx1>;interrupts = <1 3>;
};

驱动层

A.中断号:每个中断都有一个中断号,通过中断号即可区分不同的中断
B.从设备树获取中断号
unsigned int irq_of_parse_and_map(struct device_node *node, int index);
C.在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
D.中断处理函数:使用 request_irq 函数申请中断的时候需要设置中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)
E.释放中断:void free_irq(unsigned int irq, void *dev_id);

unsigned int irq_of_parse_and_map(struct device_node *node, int index);
/*功能:获得设备树中的中断号并进行映射参数:node:设备节点index:序号返回值:成功:中断号	失败:错误码
*/
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
/*
参数:irq:所申请的中断号handler:该中断号对应的中断处理函数flags:中断触发方式或处理方式 触发方式:IRQF_TRIGGER_NONE 		//无触发IRQF_TRIGGER_RISING 	//上升沿触发IRQF_TRIGGER_FALLING  //下降沿触发IRQF_TRIGGER_HIGH  	//高电平触发IRQF_TRIGGER_LOW 		//低电平触发处理方式:IRQF_DISABLED		//用于快速中断,处理中屏蔽所有中断IRQF_SHARED		  //共享中断name:中断名 /proc/interruptsdev:传递给中断例程的参数,共享中断时用于区分那个设备,一般为对应设备的结构体地址,无共享中断时写NULL
返回值:成功:0 失败:错误码
*/
typedef irqreturn_t (*irq_handler_t)(int, void *);
/*
参数:int:中断号void*:对应的申请中断时的dev_id
返回值:typedef enum irqreturn irqreturn_t;	//中断返回值类型enum irqreturn {IRQ_NONE	= (0 << 0),IRQ_HANDLED	= (1 << 0),IRQ_WAKE_THREAD	= (1 << 1),};返回IRQ_HANDLED表示处理完了,返回IRQ_NONE在共享中断表示不处理
*/
void free_irq(unsigned int irq, void *dev_id);
/*
功能:释放中断号
参数:irq:设备号dev_id:共享中断时用于区分那个设备一般强转成设备号,无共享中断时写NULL
*/

二、Linux 中断实现

驱动层按键中断代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #include "fs4412_key.h"int major = 11;
int minor = 0;
int fs4412key2_num  = 1;struct fs4412key2_dev
{struct cdev mydev;//gpio引脚int gpio;//终端号int irqno;//接收按键产生的数据struct keyvalue data;//是否有新的数据产生标志位int newflag;//中断属于异常上下文所以不可以使用阻塞引起睡眠 应该用自旋锁 实现读阻塞spinlock_t lock;wait_queue_head_t rq;
};struct fs4412key2_dev *pgmydev = NULL;int fs4412key2_open(struct inode *pnode,struct file *pfile)
{pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));return 0;
}int fs4412key2_close(struct inode *pnode,struct file *pfile)
{return 0;
}ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;int size = 0;int ret = 0;//要读取数据的大小要大于按键结构体的大小if(count < sizeof(struct keyvalue)){printk("expect read size is invalid\n");return -1;}spin_lock(&pmydev->lock);//没数据可读时if(!pmydev->newflag){if(pfile->f_flags & O_NONBLOCK){//非阻塞//解锁spin_unlock(&pmydev->lock);printk("O_NONBLOCK No Data Read\n");return -1;}else{//阻塞//解锁spin_unlock(&pmydev->lock);//判断是否来数据 不等于就睡眠 等于一就接受数据ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);if(ret){printk("Wake up by signal\n");return -ERESTARTSYS;}spin_lock(&pmydev->lock);}}//调整接收数据的大小if(count > sizeof(struct keyvalue)){size = sizeof(struct keyvalue);}else{size = count;}//将数据拷贝到用户空间ret = copy_to_user(puser,&pmydev->data,size);if(ret){spin_unlock(&pmydev->lock);printk("copy_to_user failed\n");return -1;}//拷贝成功将数据表示为变为0pmydev->newflag = 0;spin_unlock(&pmydev->lock);return size;
}unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;unsigned int mask = 0;poll_wait(pfile,&pmydev->rq,ptb);spin_lock(&pmydev->lock);//有新的数据产生就可以读if(pmydev->newflag){mask |= POLLIN | POLLRDNORM;}spin_unlock(&pmydev->lock);return mask;
}struct file_operations myops = {.owner = THIS_MODULE,.open = fs4412key2_open,.release = fs4412key2_close,.read = fs4412key2_read,.poll = fs4412key2_poll,
};
//中断处理函数
irqreturn_t key2_irq_handle(int no,void *arg)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;int status1 = 0;int status2 = 0;int status = 0;//读取两次按键gpio管脚 中间间隔1毫秒 按键消抖status1 = gpio_get_value(pmydev->gpio);mdelay(1);status2 = gpio_get_value(pmydev->gpio);//不相等中断空if(status1 != status2){return IRQ_NONE;}//按键等于0status = status1;spin_lock(&pmydev->lock);if(status == pmydev->data.status){spin_unlock(&pmydev->lock);return IRQ_NONE;}pmydev->data.code = KEY2;pmydev->data.status = status;//按键按下有新的数据产生pmydev->newflag = 1;spin_unlock(&pmydev->lock);//唤醒阻塞 把等待队列里的任务唤醒wake_up(&pmydev->rq);return IRQ_HANDLED;
}int __init fs4412key2_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);//设备树节点指针struct device_node *pnode = NULL;//找出key2的设备节点pnode = of_find_node_by_path("/mykey2_node");if(NULL == pnode){printk("find node failed\n");return -1;}pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);if(NULL == pgmydev){printk("kmallc for struct fs4412key2_dev failed\n");return -1;}//从设备树获取gpio编号 可能gpio号包含多个但咱们只有一个所以是0pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);//从设备树获取中断号pgmydev->irqno = irq_of_parse_and_map(pnode,0);/*申请设备号*/ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");if(ret){ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");if(ret){kfree(pgmydev);pgmydev = NULL;printk("get devno failed\n");return -1;}major = MAJOR(devno);//容易遗漏,注意}/*给struct cdev对象指定操作函数集*/	cdev_init(&pgmydev->mydev,&myops);/*将struct cdev对象添加到内核对应的数据结构里*/pgmydev->mydev.owner = THIS_MODULE;cdev_add(&pgmydev->mydev,devno,fs4412key2_num);//等待队列头做初始化init_waitqueue_head(&pgmydev->rq);//自旋锁初始化spin_lock_init(&pgmydev->lock);//中断申请ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);//申请失败if(ret){printk("request_irq failed\n");cdev_del(&pgmydev->mydev);//释放动态分配内存kfree(pgmydev);//指针指向空pgmydev = NULL;//unregister_chrdev_region(devno,fs4412key2_num);return -1;}return 0;
}void __exit fs4412key2_exit(void)
{dev_t devno = MKDEV(major,minor);//释放中断free_irq(pgmydev->irqno,pgmydev);cdev_del(&pgmydev->mydev);unregister_chrdev_region(devno,fs4412key2_num);kfree(pgmydev);pgmydev = NULL;
}MODULE_LICENSE("GPL");module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

按键中断应用层代码实现

#include 
#include 
#include 
#include 
#include #include #include "fs4412_key.h"int main(int argc,char *argv[])
{int fd = -1;struct keyvalue keydata = {0};int ret = 0;if(argc < 2){printf("The argument is too few\n");return 1;}fd = open(argv[1],O_RDONLY);if(fd < 0){printf("open %s failed\n",argv[1]);return 3;}while((ret = read(fd,&keydata,sizeof(keydata))) == sizeof(keydata)){if(keydata.status == KEY_DOWN){printf("Key2 is down!\n");}else{printf("Key2 is up!\n");}}close(fd);fd = -1;return 0;
}

三、中断上半部与下半部

Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。

1.如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
2.如果要处理的任务对时间敏感,可以放到上半部。
3.如果要处理的任务与硬件有关,可以放到上半部

上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢?Linux 内核提供了多种下半部机制。

1.软中断
2.tasklet
3.工作队列
任务机制
​workqueue ----- 内核线程 能睡眠 运行时间无限制
异常机制 ------- 不能睡眠 下半部执行时间不宜太长( < 1s)
​软中断 ---- 接口不方便
tasklet ----- 无具体延后时间要求时
​定时器 -----有具体延后时间要求时

四、下半部机制之tasklet ---- 基于软中断

驱动层

A.定义 taselet
struct tasklet_struct testtasklet;
B.tasklet 处理函数
void testtasklet_func(unsigned long data)
C.中断处理函数中调度 tasklet
tasklet_schedule(&testtasklet);
D. 驱动入口函数中初始化 tasklet
tasklet_init(&testtasklet, testtasklet_func, data);

//初始化tasklet
void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
//调度tasklet
void tasklet_schedule(struct tasklet_struct *t)
//参数:t:tasklet的结构体

五、按键中断下半部机制之tasklet

驱动层代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #include "fs4412_key.h"int major = 11;
int minor = 0;
int fs4412key2_num  = 1;struct fs4412key2_dev
{struct cdev mydev;int gpio;int irqno;struct keyvalue data;int newflag;spinlock_t lock;wait_queue_head_t rq;//定义tasklet_struct类型的对象struct tasklet_struct tsk;
};struct fs4412key2_dev *pgmydev = NULL;int fs4412key2_open(struct inode *pnode,struct file *pfile)
{pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));return 0;
}int fs4412key2_close(struct inode *pnode,struct file *pfile)
{return 0;
}ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;int size = 0;int ret = 0;if(count < sizeof(struct keyvalue)){printk("expect read size is invalid\n");return -1;}spin_lock(&pmydev->lock);if(!pmydev->newflag){if(pfile->f_flags & O_NONBLOCK){//非阻塞spin_unlock(&pmydev->lock);printk("O_NONBLOCK No Data Read\n");return -1;}else{//阻塞spin_unlock(&pmydev->lock);ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);if(ret){printk("Wake up by signal\n");return -ERESTARTSYS;}spin_lock(&pmydev->lock);}}if(count > sizeof(struct keyvalue)){size = sizeof(struct keyvalue);}else{size = count;}ret = copy_to_user(puser,&pmydev->data,size);if(ret){spin_unlock(&pmydev->lock);printk("copy_to_user failed\n");return -1;}pmydev->newflag = 0;spin_unlock(&pmydev->lock);return size;
}unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;unsigned int mask = 0;poll_wait(pfile,&pmydev->rq,ptb);spin_lock(&pmydev->lock);if(pmydev->newflag){mask |= POLLIN | POLLRDNORM;}spin_unlock(&pmydev->lock);return mask;
}struct file_operations myops = {.owner = THIS_MODULE,.open = fs4412key2_open,.release = fs4412key2_close,.read = fs4412key2_read,.poll = fs4412key2_poll,
};
//上半部结束时调度中断处理下半部
irqreturn_t key2_irq_handle(int no,void *arg)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;tasklet_schedule(&pmydev->tsk);return IRQ_HANDLED;
}
//中断下半部处理函数
void bottom_irq_func(unsigned long arg)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;int status1 = 0;int status2 = 0;int status = 0;status1 = gpio_get_value(pmydev->gpio);mdelay(1);status2 = gpio_get_value(pmydev->gpio);if(status1 != status2){return;}status = status1;spin_lock(&pmydev->lock);if(status == pmydev->data.status){spin_unlock(&pmydev->lock);return;}pmydev->data.code = KEY2;pmydev->data.status = status;pmydev->newflag = 1;spin_unlock(&pmydev->lock);wake_up(&pmydev->rq);return;
}int __init fs4412key2_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);struct device_node *pnode = NULL;pnode = of_find_node_by_path("/mykey2_node");if(NULL == pnode){printk("find node failed\n");return -1;}pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);if(NULL == pgmydev){printk("kmallc for struct fs4412key2_dev failed\n");return -1;}pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);pgmydev->irqno = irq_of_parse_and_map(pnode,0);/*申请设备号*/ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");if(ret){ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");if(ret){kfree(pgmydev);pgmydev = NULL;printk("get devno failed\n");return -1;}major = MAJOR(devno);//容易遗漏,注意}/*给struct cdev对象指定操作函数集*/	cdev_init(&pgmydev->mydev,&myops);/*将struct cdev对象添加到内核对应的数据结构里*/pgmydev->mydev.owner = THIS_MODULE;cdev_add(&pgmydev->mydev,devno,fs4412key2_num);init_waitqueue_head(&pgmydev->rq);spin_lock_init(&pgmydev->lock);//初始化tasklettasklet_init(&pgmydev->tsk,bottom_irq_func,(unsigned long)pgmydev);ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);if(ret){printk("request_irq failed\n");cdev_del(&pgmydev->mydev);kfree(pgmydev);pgmydev = NULL;unregister_chrdev_region(devno,fs4412key2_num);return -1;}return 0;
}void __exit fs4412key2_exit(void)
{dev_t devno = MKDEV(major,minor);free_irq(pgmydev->irqno,pgmydev);cdev_del(&pgmydev->mydev);unregister_chrdev_region(devno,fs4412key2_num);kfree(pgmydev);pgmydev = NULL;
}MODULE_LICENSE("GPL");module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

六、下半部机制之workqueue ----- 基于内核线程

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
驱动层API

A.定义一个 work_struct 结构体变量
struct work_struct testwork;
B.驱动入口函数中初始化 work
INIT_WORK(&testwork, testwork_func_t);
C.定义work 处理函数
void testwork_func_t(struct work_struct *work);
D. 中断处理函数中调度work 处理函数
schedule_work(&testwork);

//定义work_struct 结构体变量
struct work_struct  work_queue;
//初始化工作队列
INIT_WORK(struct work_struct * pwork, _func);
//参数:pwork:工作队列
//func:工作队列的底半部处理函数
//定义工作队列底半部处理函数
void work_queue_func(struct work_struct  *work);
//工作队列的调度函数
bool schedule_work(struct work_struct *work);

七、按键中断下半部机制之workqueue

驱动层代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include #include "fs4412_key.h"int major = 11;
int minor = 0;
int fs4412key2_num  = 1;struct fs4412key2_dev
{struct cdev mydev;int gpio;int irqno;struct keyvalue data;int newflag;spinlock_t lock;wait_queue_head_t rq;//struct tasklet_struct tsk;工作队列对象struct work_struct wk;
};struct fs4412key2_dev *pgmydev = NULL;int fs4412key2_open(struct inode *pnode,struct file *pfile)
{pfile->private_data =(void *) (container_of(pnode->i_cdev,struct fs4412key2_dev,mydev));return 0;
}int fs4412key2_close(struct inode *pnode,struct file *pfile)
{return 0;
}ssize_t fs4412key2_read(struct file *pfile,char __user *puser,size_t count,loff_t *p_pos)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;int size = 0;int ret = 0;if(count < sizeof(struct keyvalue)){printk("expect read size is invalid\n");return -1;}spin_lock(&pmydev->lock);if(!pmydev->newflag){if(pfile->f_flags & O_NONBLOCK){//非阻塞spin_unlock(&pmydev->lock);printk("O_NONBLOCK No Data Read\n");return -1;}else{//阻塞spin_unlock(&pmydev->lock);ret = wait_event_interruptible(pmydev->rq,pmydev->newflag == 1);if(ret){printk("Wake up by signal\n");return -ERESTARTSYS;}spin_lock(&pmydev->lock);}}if(count > sizeof(struct keyvalue)){size = sizeof(struct keyvalue);}else{size = count;}ret = copy_to_user(puser,&pmydev->data,size);if(ret){spin_unlock(&pmydev->lock);printk("copy_to_user failed\n");return -1;}pmydev->newflag = 0;spin_unlock(&pmydev->lock);return size;
}unsigned int fs4412key2_poll(struct file *pfile,poll_table *ptb)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)pfile->private_data;unsigned int mask = 0;poll_wait(pfile,&pmydev->rq,ptb);spin_lock(&pmydev->lock);if(pmydev->newflag){mask |= POLLIN | POLLRDNORM;}spin_unlock(&pmydev->lock);return mask;
}struct file_operations myops = {.owner = THIS_MODULE,.open = fs4412key2_open,.release = fs4412key2_close,.read = fs4412key2_read,.poll = fs4412key2_poll,
};
//上半部
irqreturn_t key2_irq_handle(int no,void *arg)
{struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;//tasklet_schedule(&pmydev->tsk);//任务调度下半部schedule_work(&pmydev->wk);return IRQ_HANDLED;
}//void bottom_irq_func(unsigned long arg)
//下半部
void bottom_irq_func(struct work_struct *pwk)
{//struct fs4412key2_dev *pmydev = (struct fs4412key2_dev *)arg;//已知结构体地址获取其成员地址struct fs4412key2_dev *pmydev = container_of(pwk,struct fs4412key2_dev,wk);int status1 = 0;int status2 = 0;int status = 0;status1 = gpio_get_value(pmydev->gpio);mdelay(1);status2 = gpio_get_value(pmydev->gpio);if(status1 != status2){return;}status = status1;spin_lock(&pmydev->lock);if(status == pmydev->data.status){spin_unlock(&pmydev->lock);return;}pmydev->data.code = KEY2;pmydev->data.status = status;pmydev->newflag = 1;spin_unlock(&pmydev->lock);wake_up(&pmydev->rq);return;
}int __init fs4412key2_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);struct device_node *pnode = NULL;pnode = of_find_node_by_path("/mykey2_node");if(NULL == pnode){printk("find node failed\n");return -1;}pgmydev = (struct fs4412key2_dev *)kmalloc(sizeof(struct fs4412key2_dev),GFP_KERNEL);if(NULL == pgmydev){printk("kmallc for struct fs4412key2_dev failed\n");return -1;}pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);pgmydev->irqno = irq_of_parse_and_map(pnode,0);/*申请设备号*/ret = register_chrdev_region(devno,fs4412key2_num,"fs4412key2");if(ret){ret = alloc_chrdev_region(&devno,minor,fs4412key2_num,"fs4412key2");if(ret){kfree(pgmydev);pgmydev = NULL;printk("get devno failed\n");return -1;}major = MAJOR(devno);//容易遗漏,注意}/*给struct cdev对象指定操作函数集*/	cdev_init(&pgmydev->mydev,&myops);/*将struct cdev对象添加到内核对应的数据结构里*/pgmydev->mydev.owner = THIS_MODULE;cdev_add(&pgmydev->mydev,devno,fs4412key2_num);init_waitqueue_head(&pgmydev->rq);spin_lock_init(&pgmydev->lock);//tasklet_init(&pgmydev->tsk,bottom_irq_func,(unsigned long)pgmydev);//初始化工作队列INIT_WORK(&pgmydev->wk,bottom_irq_func);ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"fs4412key2",pgmydev);if(ret){printk("request_irq failed\n");cdev_del(&pgmydev->mydev);kfree(pgmydev);pgmydev = NULL;unregister_chrdev_region(devno,fs4412key2_num);return -1;}return 0;
}void __exit fs4412key2_exit(void)
{dev_t devno = MKDEV(major,minor);free_irq(pgmydev->irqno,pgmydev);cdev_del(&pgmydev->mydev);unregister_chrdev_region(devno,fs4412key2_num);kfree(pgmydev);pgmydev = NULL;
}MODULE_LICENSE("GPL");module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...