国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

Driver:內核等待隊列、按鍵延時去抖、按下和釋放去抖、用戶態對設備的非阻塞方式訪問

2019-11-06 06:21:53
字體:
來源:轉載
供稿:網友
1、內核等待隊列    recv (...)  // 網卡收到數據該函數立即返回                // 未收到數據阻塞睡眠等待,一旦有數據立即讀取        在linux驅動程序中,可以使用等待隊列來實現進程阻塞。    等待隊列可以看作保存進程的容器,在阻塞進程時,將進程放入等待隊列,當進程被喚醒時,從等待隊列中取出進程。實際上,信號量等對進程的阻塞在內核中也依賴等待隊列來實現。    核心數據結構:        'wait_queue_head_t' - 是一個線性鏈式存儲結構        typedef struct __wait_queue_headwait_queue_head_t;    使用步驟:        1) 定義一個等待隊列頭變量:            wait_queue_head_tbtn_wqh; // 定義變量        2) 初始化等待隊列頭:           init_waitqueue_head (&btn_wqh); // 初始化變量            init_waitqueue_head (wait_queue_head_t *q);            /* 定義并初始化等待隊列頭 */            #define DECLARE_WAIT_QUEUE_HEAD(name) ...            /* 定義和初始化等待隊列 */            DECLARE_WAITQUEUE(name, task);            // 定義并初始化一個名稱為name的等待隊列,task通常被設置為代表當前進程的current指針        3) 使用:(有按鍵事件就返回,沒有按鍵事件就睡眠)            /* 進入睡眠狀態 */           wait_event(wq, condition);                // 當condition為真時,立即返回;否則進程進入TASK_UNINTERRUPTIBLE類型的睡眠狀態,并掛在queue指定的等待隊列數據鏈上            wait_event_interruptible(wq, condition);                 // 當condition為真時,立即返回;否則進程進入TASK_INTERRUPTIBLE類型的睡眠狀態,并掛在queue指定的等待隊列數據鏈上            wait_event_timeout(wq, condition, timeout);                // 當condition為真時,立即返回;否則進程進入TASK_KILLABLE類型的睡眠狀態,并掛在queue指定的等待隊列數據鏈上                @wq:<==> btn_wqh,等待隊列頭變量;                @condition:條件,該表達式為true,不睡眠直接跳過該函數;                                  該表達式為false,讓進程進入睡眠狀態。            /* 喚醒 */           wake_up(x);                // 喚醒由queue指向的等待隊列數據鏈中的所有睡眠類型的等待進程            wake_up_interruptible(x);                // 喚醒由queue指向的等待隊列數據鏈中的所有睡眠類型為TASK_INTERRUPTIBLE的等待進程    內核中:    'current:指針,指向CPU正在執行的進程。    struct task_struct: 內核中關于進程的數據結構。        有一個學生,數據庫中就有一條學生記錄;        有一個進程,內核中就有一條進程的記錄;   TASK_INTERRUPTIBLE: 可中斷的睡眠狀態   schedule: 內核進行重新調度
/** 代碼演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定義信號量 */struct semaphore btn_sem;/* 定義等待隊列頭 */wait_queue_head_t btn_wqh;int ev_PRess = 0; // 如果有按鍵值供用戶空間讀 = 1,沒有值可讀 = 0char key_buf; // 按鍵緩沖區 == 1個字節/* 中斷數據結構 */typedef struct btn_desc {char	key_val; // 鍵值int	irq;	// 中斷號char*	name;	// 名稱} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, "Key3"},{0x40, IRQ_GPIO_B_START +  9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 獲取信號量 */// 1. 獲取信號量成功,返回0// 2. 被信號打斷,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 釋放信號量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有數據供用戶空間讀,返回數據給用戶空間  無數據供用戶空間讀,調用者進程進入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 讀走緩沖數據后的清0標記return len; // 讀取成功的字節個數}struct file_Operations btn_fops = {.owner   = THIS_MODULE,.open	= btn_open,.release = btn_release,.read	= btn_read, // 新增};/* 中斷處理函數 */irqreturn_t btn_isr (int irq, void* dev){btn_desc_t* pdata = (btn_desc_t*)dev;/* 保存按鍵值 */key_buf = pdata->key_val;ev_press = 1;/* 喚醒因按鍵值不足而睡眠的進程 */wake_up_interruptible (&btn_wqh);return IRQ_HANDLED;}int __init btn_drv_init (void){int i = 0;/* 1.申請設備號 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函數/* 3.注冊cdev */cdev_add (&btn_cdev, dev, 1);/* 4.創建設備文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "btns");/* 初始化信號量 - 只允許1個持有者 */sema_init (&btn_sem, 1);/* 初始化等待隊列頭 */init_waitqueue_head (&btn_wqh);/* 注冊中斷處理函數 - 可以處理4個按鍵中斷事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}}return 0;}void __exit btn_drv_exit (void){int i = 0;/* 注銷中斷處理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));}/* 銷毀設備文件 */device_destroy (cls, dev);class_destroy (cls);/* 注銷cdev */cdev_del (&btn_cdev);/* 釋放設備號 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 測試代碼 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;fd = open ("/dev/btns", O_RDWR);if (fd < 0) {perror ("open /dev/btns failed");return -1;}printf ("open /dev/btns success.../n");/* 新增 */ while (1) {read (fd, &key, sizeof (char));printf ("user space key = %#x/n", key);}close (fd);return 0;}2、延時去抖    按鍵抖動的延時間隔,可以用'示波器'抓波形得出。    如果小于10ms,在定時器設置就延時10ms;    假如抓出15ms,在定時器設置就延時20ms,以此類推。
/** 代碼演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定義信號量 */struct semaphore btn_sem;/* 定義等待隊列頭 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按鍵值供用戶空間讀 = 1,沒有值可讀 = 0char key_buf; // 按鍵緩沖區 == 1個字節/* 定義定時器變量 */struct timer_list btn_timer;/* 中斷數據結構 */typedef struct btn_desc {char	key_val; // 鍵值int	irq;	// 中斷號char*	name;	// 名稱} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, "Key3"},{0x40, IRQ_GPIO_B_START +  9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 獲取信號量 */// 1. 獲取信號量成功,返回0// 2. 被信號打斷,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 釋放信號量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有數據供用戶空間讀,返回數據給用戶空間  無數據供用戶空間讀,調用者進程進入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 讀走緩沖數據后的清0標記return len; // 讀取成功的字節個數}struct file_operations btn_fops = {.owner   = THIS_MODULE,.open	= btn_open,.release = btn_release,.read	= btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中斷處理函數 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){btn_desc_t* pdata = (btn_desc_t*)data; // 這里寫成dev了/* 保存按鍵值 */key_buf = pdata->key_val;ev_press = 1;/* 喚醒因按鍵值不足而睡眠的進程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申請設備號 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函數/* 3.注冊cdev */cdev_add (&btn_cdev, dev, 1);/* 4.創建設備文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信號量 - 只允許1個持有者 */sema_init (&btn_sem, 1);/* 初始化等待隊列頭 */init_waitqueue_head (&btn_wqh);/* 注冊中斷處理函數 - 可以處理4個按鍵中斷事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注銷中斷處理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));}/* 銷毀設備文件 */device_destroy (cls, dev);class_destroy (cls);/* 注銷cdev */cdev_del (&btn_cdev);/* 釋放設備號 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 測試代碼 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;fd = open ("/dev/buttons", O_RDWR);if (fd < 0) {perror ("open /dev/buttons failed");return -1;}printf ("open /dev/btns success.../n");/* 新增 */ while (1) {read (fd, &key, sizeof (char));printf ("user space key = %#x/n", key);}close (fd);return 0;}3、按下和釋放去抖觸發中斷    'IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING'   request_irq () // 可以指定雙沿觸發    保存按鍵前要判斷按下觸發還是釋放觸發    通過管腳狀態來判斷。
/** 代碼演示 **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>#include <linux/gpio.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定義信號量 */struct semaphore btn_sem;/* 定義等待隊列頭 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按鍵值供用戶空間讀 = 1,沒有值可讀 = 0char key_buf; // 按鍵緩沖區 == 1個字節/* 定義定時器變量 */struct timer_list btn_timer;/* 中斷數據結構 */typedef struct btn_desc {char	key_val; // 鍵值int	irq;	// 中斷號int	gpio;	// 管腳編號char*	name;	// 名稱} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},{0x40, IRQ_GPIO_B_START +  9, PAD_GPIO_B +  9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 獲取信號量 */// 1. 獲取信號量成功,返回0// 2. 被信號打斷,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 釋放信號量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 有數據供用戶空間讀,返回數據給用戶空間  無數據供用戶空間讀,調用者進程進入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 讀走緩沖數據后的清0標記return len; // 讀取成功的字節個數}struct file_operations btn_fops = {.owner   = THIS_MODULE,.open	= btn_open,.release = btn_release,.read	= btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中斷處理函數 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){int stat = 0;/* 確定是哪個按鍵觸發的 */btn_desc_t* pdata = (btn_desc_t*)data;/* 根據管腳電平狀態,判斷是按下觸發?釋放觸發? */stat = gpio_get_value (pdata->gpio); // 獲取管腳上的電平狀態:0低非0高/* 保存按鍵值 stat==100 ---> !stat==0 ---> !!stat==1 */key_buf = pdata->key_val + !!stat;ev_press = 1;/* 喚醒因按鍵值不足而睡眠的進程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申請設備號 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函數/* 3.注冊cdev */cdev_add (&btn_cdev, dev, 1);/* 4.創建設備文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信號量 - 只允許1個持有者 */sema_init (&btn_sem, 1);/* 初始化等待隊列頭 */init_waitqueue_head (&btn_wqh);/* 注冊中斷處理函數 - 可以處理4個按鍵中斷事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}/* GPIO管腳的申請 */gpio_request (buttons[i].gpio, buttons[i].name);}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注銷中斷處理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));gpio_free (buttons[i].gpio);}/* 銷毀設備文件 */device_destroy (cls, dev);class_destroy (cls);/* 注銷cdev */cdev_del (&btn_cdev);/* 釋放設備號 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);4、用戶態對設備的非阻塞方式訪問    實際上,應用程序并不關心驅動里面read/write具體實現,只管調用并獲取返回值。    如果設備沒有準備好數據給應用程序讀或者沒有準備好接收用戶程序寫,驅動程序應當阻塞進程,使它進入睡眠,直到請求可以得到滿足。    open ("/dev/xxx", O_RDWR);// 默認是阻塞方式訪問    open ("/dev/xxx", O_RDWR |O_NONBLOCK); // 非阻塞方式進行訪問。    struct file {        // open函數的系統調用從f_op指針指向了file_operations里面的open函數    const struct file_operations *f_op;         // 記錄了open(..., flag)時flag的絕大多數信息   unsigned int f_flags;     };    '簡言之:f_flags 記錄了文件open時的打開方式。'    #ifndef O_NONBLOCK   #define O_NONBLOCK 00004000    #endif
/** 代碼演示 - btn_drv.c **/#include <linux/init.h>#include <linux/module.h>#include <linux/fs.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/delay.h>#include <linux/interrupt.h>#include <linux/uaccess.h>#include <mach/platform.h>#include <linux/sched.h>#include <linux/gpio.h>MODULE_LICENSE ("GPL");dev_t dev;struct cdev btn_cdev;struct class *cls = NULL;/* 定義信號量 */struct semaphore btn_sem;/* 定義等待隊列頭 */wait_queue_head_t btn_wqh;int ev_press = 0; // 如果有按鍵值供用戶空間讀 = 1,沒有值可讀 = 0char key_buf; // 按鍵緩沖區 == 1個字節/* 定義定時器變量 */struct timer_list btn_timer;/* 中斷數據結構 */typedef struct btn_desc {char	key_val; // 鍵值int	irq;	// 中斷號int	gpio;	// 管腳編號char*	name;	// 名稱} btn_desc_t;btn_desc_t buttons[] = {{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},{0x40, IRQ_GPIO_B_START +  9, PAD_GPIO_B +  9, "Key4"},};int btn_open (struct inode* inode, struct file* filp){/* 獲取信號量 */// 1. 獲取信號量成功,返回0// 2. 被信號打斷,返回非0if (down_interruptible (&btn_sem))return -EAGAIN;return 0;}int btn_release (struct inode* inode, struct file* filp){/* 釋放信號量 */up (&btn_sem);return 0;}ssize_t btn_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) // 新增{/* 判斷open文件時是否加了O_NONBLOCK的打開方式 */if ( (filp->f_flags & O_NONBLOCK) && ev_press == 0)return -EAGAIN;/* 有數據供用戶空間讀,返回數據給用戶空間  無數據供用戶空間讀,調用者進程進入睡眠 */wait_event_interruptible (btn_wqh, ev_press);if ( copy_to_user (buf, &key_buf, len) )printk ("copy_to_user failed.../n");ev_press = 0; // 讀走緩沖數據后的清0標記return len; // 讀取成功的字節個數}struct file_operations btn_fops = {.owner   = THIS_MODULE,.open	= btn_open,.release = btn_release,.read	= btn_read, };int interval = 100;module_param (interval, int, 0644);/* 中斷處理函數 */irqreturn_t btn_isr (int irq, void* dev){btn_timer.data = (unsigned long)dev;mod_timer (&btn_timer, jiffies + HZ/interval);return IRQ_HANDLED;}void btn_timer_func (unsigned long data){int stat = 0;/* 確定是哪個按鍵觸發的 */btn_desc_t* pdata = (btn_desc_t*)data;/* 根據管腳電平狀態,判斷是按下觸發?釋放觸發? */stat = gpio_get_value (pdata->gpio); // 獲取管腳上的電平狀態:0低非0高/* 保存按鍵值 stat==100 ---> !stat==0 ---> !!stat==1 */key_buf = pdata->key_val + !!stat;ev_press = 1;/* 喚醒因按鍵值不足而睡眠的進程 */wake_up_interruptible (&btn_wqh);}int __init btn_drv_init (void){int i = 0;/* 1.申請設備號 */alloc_chrdev_region (&dev, 0, 1, "mybtns");/* 2.初始化cdev */cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函數/* 3.注冊cdev */cdev_add (&btn_cdev, dev, 1);/* 4.創建設備文件 */cls = class_create (THIS_MODULE, "mybtns");device_create (cls, NULL, dev, NULL, "buttons");/* 初始化信號量 - 只允許1個持有者 */sema_init (&btn_sem, 1);/* 初始化等待隊列頭 */init_waitqueue_head (&btn_wqh);/* 注冊中斷處理函數 - 可以處理4個按鍵中斷事件 */for (; i < ARRAY_SIZE (buttons); i++) {if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, buttons[i].name, &(buttons[i]))) {printk ("request_irq failed!/n");return -EAGAIN;}/* GPIO管腳的申請 */gpio_request (buttons[i].gpio, buttons[i].name);}/* 初始化timer */init_timer (&btn_timer);btn_timer.function = btn_timer_func;return 0;}void __exit btn_drv_exit (void){int i = 0;/* 取消timer */del_timer (&btn_timer);/* 注銷中斷處理 */for (; i < ARRAY_SIZE (buttons); i++) {free_irq (buttons[i].irq, &(buttons[i]));gpio_free (buttons[i].gpio);}/* 銷毀設備文件 */device_destroy (cls, dev);class_destroy (cls);/* 注銷cdev */cdev_del (&btn_cdev);/* 釋放設備號 */unregister_chrdev_region (dev, 1);}module_init (btn_drv_init);module_exit (btn_drv_exit);/** 測試代碼 - test.c **/#include <stdio.h>#include <fcntl.h>int main (void){int fd = 0;char key = 0;/* 非阻塞方式+可讀可寫方式打開 */fd = open ("/dev/buttons", O_RDWR | O_NONBLOCK);if (fd < 0) {perror ("open /dev/buttons failed");return -1;}printf ("open /dev/btns success.../n");while (1) {if (read (fd, &key, sizeof (char)) > 0)printf ("user space key = %#x/n", key);elseperror ("read failed");sleep (3);}close (fd);return 0;}
上一篇:樣式表

下一篇:spring對jdbc事務控制

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 连城县| 丹凤县| 沅江市| 庄河市| 太保市| 大冶市| 涟水县| 都安| 塔河县| 迭部县| 高台县| 延川县| 马关县| 江油市| 揭阳市| 怀集县| 溧水县| 随州市| 宣城市| 新绛县| 胶州市| 富平县| 张家港市| 周至县| 南和县| 新沂市| 临江市| 普兰店市| 常山县| 南平市| 弋阳县| 荥阳市| 南陵县| 白银市| 石嘴山市| 泗洪县| 溧水县| 建宁县| 芦溪县| 如皋市| 建宁县|