嵌入式Linux驱动程序开发(二)实例剖析

姓名:薛绍宏     学号:19020100016    学院:电子工程学院

转自:https://blog.csdn.net/iteye_2060/article/details/82089821?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162722233316780264011150%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162722233316780264011150&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-82089821.pc_search_result_control_group&utm_term=%E5%B5%8C%E5%85%A5%E5%BC%8Flinux%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91&spm=1018.2226.3001.4449

【嵌牛导读】本文介绍了嵌入式Linux驱动程序开发的一些实例和剖析

【嵌牛鼻子】嵌入式Linux驱动程序开发

【嵌牛提问】嵌入式Linux驱动程序开发实例如何剖析?

【嵌牛正文】

9.实例剖析

9.1实例一

9.1.1驱动程序

/******************************

*LED_Driver 2007/09/20

*****************************/

#include<linux/config.h>

#include<linux/kernel.h>

#include<linux/sched.h>

#include<linux/timer.h>

#include<linux/init.h>

#include<linux/module.h>

#include<asm/hardware.h>

#defineGPIO_LED_MAJOR 97

#defineARM_GPIO_LED_DEBUG

#defineARM_GPIO_LED (GPIO96)

#defineLED_ON 0

#defineLED_OFF 1

#definectl_GPIO_LED1 1

#defineVERSION "ARM_GPIO_LED_2007/09/20"

voidshowversion(void)

{

printk("*********************************************\n");

printk("\t %s \t\n",VERSION);

printk("********************************************\n\n");

}

//------------------- READ ------------------------

ssize_tGPIO_LED_read (struct file * file ,char * buf, size_t count, loff_t * f_ops)

{

#ifdef ARM_GPIO_LED_DEBUG

printk ("GPIO_LED_read [--kernel--]\n");

#endif

return count;

}

//------------------- WRITE -----------------------

ssize_tGPIO_LED_write (struct file * file ,const char * buf, size_t count, loff_t *f_ops)

{

#ifdef ARM_GPIO_LED_DEBUG

printk("GPIO_LED_write [ --kernel--]\n");

#endif

return count;

}

//------------------- IOCTL -----------------------

ssize_tGPIO_LED_ioctl (struct inode * inode ,struct file * file, unsigned int cmd,long data)

{

#ifdef ARM_GPIO_LED_DEBUG

printk("GPIO_LED_ioctl [ --kernel--]\n");

#endif

switch (cmd)

{

case LED_ON : {GPCR3 |= 0x1;break;}

case LED_OFF: {GPSR3 |= 0x1;break;}

default :

{printk ("lcdcontrol : no cmd run [ --kernel--]\n"); return (-EINVAL);}

}

return 0;

}

//------------------- OPEN ------------------------

ssize_tGPIO_LED_open (struct inode * inode ,struct file * file)

{

#ifdef ARM_GPIO_LED_DEBUG

printk("GPIO_LED_open [ --kernel--]\n");

#endif

MOD_INC_USE_COUNT;

return 0;

}

//------------------- RELEASE/CLOSE ---------------

ssize_tGPIO_LED_release (struct inode * inode,struct file * file)

{

#ifdef ARM_GPIO_LED_DEBUG

printk ("GPIO_LED_release [ --kernel--]\n");

#endif

MOD_DEC_USE_COUNT;

return 0;

}

//-------------------------------------------------

structfile_operations GPIO_LED_ctl_ops ={

open: GPIO_LED_open,

read: GPIO_LED_read,

write: GPIO_LED_write,

ioctl: GPIO_LED_ioctl,

release: GPIO_LED_release,

};

//------------------- INIT ------------------------

staticint __init HW_GPIO_LED_CTL_init(void)

{

int ret = -ENODEV;

printk("Driver Loding.....................\n\n");

showversion();

// init GPIO

GPDR3 |= 0x00000001; // SET GPIO96 OUTPUTMODE

GPSR3 |= 0x00000001; // OFF THE LED

#ifdef ARM_GPIO_LED_DEBUG

printk (" GPLR3=%x\n",GPLR3);

printk (" GPDR3=%x \n",GPDR3);

#endif

ret = devfs_register_chrdev(GPIO_LED_MAJOR,"led_drv", &GPIO_LED_ctl_ops);

if( ret < 0 )

{

printk (" ARM: init_module failedwith %d\n [ --kernel--]", ret);

return ret;

}

else

{

printk(" ARM gpio_led_driverregister success!!! [ --kernel--]\n");

}

return ret;

}

staticint __init ARM_GPIO_LED_CTL_init(void)

{

intret = -ENODEV;

#ifdef ARM_GPIO_LED_DEBUG

printk("ARM_GPIO_LED_CTL_init [ --kernel--]\n");

#endif

ret = HW_GPIO_LED_CTL_init();

if (ret)

return ret;

return 0;

}

staticvoid __exit cleanup_GPIO_LED_ctl(void)

{

#ifdef ARM_GPIO_LED_DEBUG

printk("cleanup_GPIO_LED_ctl [ --kernel--]\n");

#endif

devfs_unregister_chrdev (GPIO_LED_MAJOR,"gpio_led_ctl" );

}

MODULE_DESCRIPTION("GPIO_led driver module");

MODULE_AUTHOR("zsm");

MODULE_LICENSE("GPL");//GPL协议证书信息

module_init(ARM_GPIO_LED_CTL_init);

module_exit(cleanup_GPIO_LED_ctl);

9.1.2用户程序

在编写用户应用程序过程中,考虑通过接口open()函数打开设备,再通过接口ioctl()函数来实现对LED的控制功能。

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <fcntl.h> // open() close()

#include <unistd.h> // read() write()

#define DEVICE_NAME "/dev/led_drv"

#define LED_ON0

#define LED_OFF 1

int main(void)

{

intfd;

int ret;

char *i;

printf("\nstart GPIO_led_driver test\n\n");

fd= open(DEVICE_NAME, O_RDWR);

printf("fd= %d\n",fd);

if(fd == -1)

{

printf("open device %s error\n",DEVICE_NAME);

}

else

{

while(1)

{ ioctl(fd,LED_OFF);

sleep(1);

ioctl(fd,LED_ON);

sleep(1);

}

ret =close(fd);

printf("ret=%d\n",ret);

printf("close led_driver test\n");

}

return 0;

}

9.1.3执行效果

(1)Create:

[root@OURSELEC usb]# mknod /dev/led_drv c 97 0

(2)view:

[root@OURSELEC usb]# ls -l /dev/led_drv

crw-r--r--1 root root 97,0 Jan 1 01:29 /dev/led_drv

(3)insmod:

[root@OURSELEC usb]# insmod led_drv.o

Using led_drv.o

ARM_GPIO_LED_CTL_init [ --kernel--]

Driver Loding .....................

*********************************************

ARM_GPIO_LED_2007/09/20

*********************************************

GPLR3=73e7fd

GPDR3=1efffc3

ARMgpio_led_driver register success!!! [ --kernel--]

(4)./test_led:

[root@OURSELEC usb]# ./test_led

startGPIO_led_driver test

GPIO_LED_open[ --kernel--]

fd = 3

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_ioctl [ --kernel--]

GPIO_LED_release [ --kernel--]

(5) remove led_drv mode:

[root@OURSELEC usb]# rmmod led_drv

cleanup_GPIO_LED_ctl [ --kernel--]

9.2实例二

/*************************************

NAME:gt2440_leds.c

COPYRIGHT:www.e-online.cc

*************************************/

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <mach/regs-gpio.h>

#include <mach/hardware.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/mm.h>

#include <linux/fs.h>

#include <linux/types.h>

#include <linux/delay.h>

#include <linux/moduleparam.h>

#include <linux/slab.h>

#include <linux/errno.h>

#include <linux/ioctl.h>

#include <linux/cdev.h>

#include <linux/string.h>

#include <linux/list.h>

#include <linux/pci.h>

#include <asm/uaccess.h>

#include <asm/atomic.h>

#include <asm/unistd.h>

#define DEVICE_NAME "leds"

/* 应用程序执行ioctl(fd, cmd, arg)时的第2个参数 */

#define IOCTL_LED_ON 1

#define IOCTL_LED_OFF 0

/* 用来指定LED所用的GPIO引脚 */

static unsigned long led_table [] =

{

S3C2410_GPB5,

S3C2410_GPB6,

S3C2410_GPB7,

S3C2410_GPB8,

};

/* 用来指定GPIO引脚的功能:输出 */

static unsigned int led_cfg_table [] =

{

S3C2410_GPB5_OUTP,

S3C2410_GPB6_OUTP,

S3C2410_GPB7_OUTP,

S3C2410_GPB8_OUTP,

};

static int gt2440_leds_ioctl(

structinode *inode,

structfile *file,

unsignedint cmd,

unsignedlong arg)

{

if(arg > 4)

{

return-EINVAL;

}

switch(cmd)

{

caseIOCTL_LED_ON:

//设置指定引脚的输出电平为0

s3c2410_gpio_setpin(led_table[arg], 0);

return0;

caseIOCTL_LED_OFF:

//设置指定引脚的输出电平为1

s3c2410_gpio_setpin(led_table[arg], 1);

return0;

default:

return-EINVAL;

}

}

static struct file_operations dev_fops = {

.owner = THIS_MODULE,

.ioctl = gt2440_leds_ioctl,

};

static struct miscdevice misc = {

.minor= MISC_DYNAMIC_MINOR,

.name= DEVICE_NAME,

.fops= &dev_fops,

};

static int __init dev_init(void)

{

intret;

inti;

for(i = 0; i < 4; i++)

{

s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);

s3c2410_gpio_setpin(led_table[i], 0);

}

ret= misc_register(&misc);

printk(DEVICE_NAME" initialized\n");

returnret;

}

static void __exit dev_exit(void)

{

misc_deregister(&misc);

}

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("www.e-online.cc");

MODULE_DESCRIPTION("LEDS control forGT2440 Board");

10块设备驱动程序的编写

块设备文件通常指一些需要以块(如512字节)的方式写入的设备,如IDE硬盘、SCSI硬盘、光驱等。它的驱动程序的编写过程与字符型设备驱动程序的编写有很大的区别。

为了把各种块设备的操作请求队列有效地组织起来,内核中设置了一个结构数组blk_dev,该数组中的元素类型是blk_dev_struct结构。这个结构由三个成分组成,其主体是执行操作的请求队列request_queue,还有一个函数指针queue。当这个指针不为0时,就调用这个函数来找到具体设备的请求队列。块设备驱动程序描述符是一个包含在<linux/blkdev.h>中的blk_dev_struct类型的数据结构,其定义如下所示:

struct blk_dev_struct {

request_queue_t request_queue;

queue_proc *queue;

void *date;

};

在这个结构中,请求队列request_queue是主体,包含了初始化之后的I/O 请求队列。

所有块设备的描述符都存放在blk_dev表struct blk_dev_structblk_dev[MAX_BLKDEV]中;每个块设备都对应着数组中的一项,可以使用主设备号进行检索。每当用户进程对一个块设备发出一个读写请求时,首先调用块设备所公用的函数generic_file_read(),generic_file_write()。如果数据存在在缓冲区中或缓冲区还可以存放数据,那么就同缓冲区进行数据交换。否则,系统会将相应的请求队列结构添加到其对应项的blk_dev_struct中,如下图所示:

10.1块设备驱动编写流程

块设备驱动程序的编写流程同字符设备驱动程序的编写流程很类似,也包括了注册和使

用两部分。但与字符驱动设备所不同的是,块设备驱动程序包括一个request请求队列。它是

当内核安排一次数据传输时在列表中的一个请求队列,用以最大化系统性能为原则进行排序。

块设备驱动程序流程图

10.2重要数据结构

Linux系统中有一个名为blkdevs的结构数组,它描述了一系列在系统中登记的块设备。数组blkdevs也使用设备的主设备号作为索引,其元素类型是device_struct结构。该结构中包括指向已登记的设备驱动程序名的指针和指向block_device_operations结构的指针。在block_device_operations结构中包含指向有关操作的函数指针。所以,该结构就是连接抽象的块设备操作与具体块设备类型的操作之间的枢纽。

10.2.1 struct bio

一个bio结构体是在通用块层或更底层对块设备i/o操作的的表示单位。通常1个bio对应1个I/O请求.

struct bio {

sector_tbi_sector; /* device address in 512 byte

sectors */

structbio *bi_next; /*request queue link */

structblock_device *bi_bdev;

unsignedlongbi_flags; /* status, command, etc */

unsignedlongbi_rw; /* bottom bits READ/WRITE,

* top bits priority

*/

unsignedshort bi_vcnt; /*how many bio_vec's */

unsignedshort bi_idx;/* current index into bvl_vec */

/* Numberofsegments in this BIO after

*physical address coalescing is performed.

*/

unsignedint bi_phys_segments;

unsignedint bi_size; /*residual I/O count */

/*

* Tokeep track of the max segment size, weaccount for the

*sizes of the first and last mergeablesegments in this bio.

*/

unsignedint bi_seg_front_size;

unsignedint bi_seg_back_size;

unsignedintbi_max_vecs; /* max bvl_vecs we can hold */

unsignedintbi_comp_cpu; /* completion CPU */

atomic_tbi_cnt; /*pin count*/

structbio_vec*bi_io_vec; /* the actual vec list */

bio_end_io_t*bi_end_io;

void*bi_private;

#if defined(CONFIG_BLK_DEV_INTEGRITY)

structbio_integrity_payload *bi_integrity; /*data integrity */

#endif

bio_destructor_t *bi_destructor; /* destructor */

/*

* Wecan inline a number of vecs at the end ofthe bio, to avoid

*double allocations for a small number ofbio_vecs. This member

* MUSTobviously be kept at the very end ofthe bio.

*/

structbio_vec bi_inline_vecs[0];

};

10.2.2 struct gendisk

struct gendisk { //表示一个独立的磁盘设备或分区

intmajor;/* major number of driver */

intfirst_minor; /*starting minor number*/

intminors; /* maximumnumber ofminors, =1 for

*disks that can't be partitioned. 每一个分区都有一个minor号*/

chardisk_name[DISK_NAME_LEN]; /* name ofmajor driver */

structdisk_part_tbl *part_tbl;

structhd_struct part0;

structblock_device_operations *fops;

structrequest_queue*queue;

void*private_data;

int flags;

struct device*driverfs_dev; // FIXME: remove

struct kobject*slave_dir;

structtimer_rand_state *random;

atomic_tsync_io; /* RAID */

structwork_struct async_notify;

#ifdefCONFIG_BLK_DEV_INTEGRITY

structblk_integrity *integrity;

#endif

int node_id;

};

struct device_struct {

const char *name;

struct file_operations *chops;

};

static struct device_structblkdevs[MAX_BLKDEV];

struct sbull_dev {

void **data;

int quantum;// thecurrent quantum size

int qset;// the current array size

unsigned long size;

unsigned int access_key;// used by sbulluid and sbullpriv

unsigned int usage;// lock the device while using it

unsigned int new_msg;

struct sbull_dev *next;// next listitem

};

与字符设备驱动程序一样,块设备驱动程序也包含一个file_operation结构,其结构定义一般如下所示:

struct file_operation blk_fops = {

NULL,//seek

block_read,//内核函数

block_write,//内核函数

NULL,//readdir

NULL,//poll

sbull_ioctl,// ioctl

NULL,//mmap

sbull_open,//open

NULL,//flush

sbull_release,//release

block_fsync,//内核函数

NULL,//fasync

sbull_check_media_change,//check media change

NULL,//revalidate

NULL,//lock

};

所有的块驱动程序都调用内核函数block_read()、block_write(),block_fsync()函数,所以在块设备驱动程序入口中不包含这些函数,只需包括ioctl()、open()

和release()函数即可。

(1)设备初始化

块设备的初始化过程要比字符设备复杂,它既需要像字符设备一样在引导内核时完成一定的

工作,还需要在内核编译时增加一些内容。块设备驱动程序初始化时,由驱动程序的init()完成。

块设备驱动程序初始化的工作主要包括:

· 检查硬件是否存在;

· 登记主设备号;

· 将fops结构的指针传递给内核;

· 利用register_blkdev()函数对设备进行注册:

if(register_blkdev(sbull_MAJOR,“sbull”,&sbull_fops)) {

printk(“Registering block device major:%d failed\n”,sbull_MAJOR);

return-EIO;

};

· 将request()函数的地址传递给内核:

blk_dev[sbull_MAJOR].request_fn= DEVICE_REQUEST;

· 将块设备驱动程序的数据容量传递给缓冲区:

#define sbull_HARDS_SIZE 512

#define sbull_BLOCK_SIZE 1024

static int sbull_hard = sbull_HARDS_SIZE;

static int sbull_soft = sbull_BLOCK_SIZE;

hardsect_size[sbull_MAJOR] = &sbull_hard;

blksize_size[sbull_MAJOR] = &sbull_soft;

在块设备驱动程序内核编译时,应把下列宏加到blk.h文件中:

#define MAJOR_NR sbull_MAJOR

#define DEVICE_NAME “sbull”

#define DEVICE_REQUEST sbull_request

#define DEVICE_NR(device) (MINOR(device))

#define DEVICE_ON(device)

#define DEVICE_OFF(device)

(2)request操作

Request操作涉及一个重要的数据结构如下。

struct request {

kdev_t rq_dev;

int cmd; // 读或写

int errors;

unsigned long sector;

char *buffer;

struct request *next;

};

对于具体的块设备,函数指针request_fn当然是不同的。块设备的读写操作都是由request()函数完成。所有的读写请求都存储在request结构的链表中。request()函数利用CURRENT宏

检查当前的请求。request()函数从INIT_REQUEST宏命令开始(它也在blk.h中定义),它对请求队列进行检查,保证请求队列中至少有一个请求在等待处理。如果没有请求(即CURRENT = 0),则INIT_REQUEST宏命令将使request()函数返回,任务结束。

假定队列中至少有一个请求,request()函数现在应处理队列中的第一个请求,当处理完

请求后,request()函数将调用end_request()函数。如果成功地完成了读写操作,那么应该用参数值1 调用end_request()函数;如果读写操作不成功,那么以参数值0 调用end_request()函数。如果队列中还有其他请求,那么将CURRENT 指针设为指向下一个请求。执行end_request()函数后,request()函数回到循环的起点,对下一个请求重复上面的处理过程。

(3)打开操作

(4)释放设备操作

(5)ioctl操作

10.3中断编程

很多Linux 的驱动都是通过中断的方式来进行内核和硬件的交互。

这是驱动程序申请中断和释放中断的调用。在include/linux/sched.h里声明。

request_irq()调用的定义:

int request_irq(unsigned int irq,

void (*handler)(int irq, void*dev_id, struct pt_regs *regs),

unsigned long irqflags,const char* devname,oid *dev_id);

irq 是要申请的硬件中断号。在Intel平台,范围是0~15。handler 是向系统登记的中断处理函数。这是一个回调函数,中断发生时,系统调用这个函数,传入的参数包括硬件中断号,device id,寄存器值。dev_id就是下面的request_irq时传递给系统的参数dev_id。irqflags是中断处理的一些属性。比较重要的有SA_INTERRUPT,标明中断处理程序是快速处理程序(设置SA_INTERRUPT)还是慢速处理程序(不设置SA_INTERRUPT)。快速处理程序

被调用时屏蔽所有中断。慢速处理程序不屏蔽。还有一个SA_SHIRQ 属性,设置了以后运行多个设备共享中断。dev_id在中断共享时会用到。一般设置为这个设备的device结构本身或者NULL。中断处理程序可以用dev_id找到相应的控制这个中断的设备,或者用irq2dev_map

找到中断对应的设备。void free_irq(unsigned int irq,void *dev_id);

10.4一个简单的块设备驱动

通过写一个建立在内存中的块设备驱动,来学习linux内核和相关设备驱动知识

#defineSIMP_BLKDEV_DISKNAME "simp_blkdev"

#defineSIMP_BLKDEV_BYTES (16*1024*1024)// 使用宏定义了块设备的大小,定为16M

#defineSIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

structblock_device_operations simp_blkdev_fops = {

.owner = THIS_MODULE,

};// gendisk结构需要设置fops指针,虽然我们用不到,但该设还是要设的

static structgendisk *simp_blkdev_disk;

static structrequest_queue *simp_blkdev_queue;// 指向块设备需要的请求队列

unsigned charsimp_blkdev_data[SIMP_BLKDEV_BYTES];

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

module_init(simp_blkdev_init);//然后申明模块的入口和出口

module_exit(simp_blkdev_exit);

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

static int __initsimp_blkdev_init(void) //在入口处添加这个设备、出口处删除这个设备

{

simp_blkdev_disk = alloc_disk(1); //在添加设备之前我们需要申请这个设备的资源,这用到了alloc_disk()函数

strcpy(simp_blkdev_disk->disk_name,SIMP_BLKDEV_DISKNAME); //设备有关的属性也是需要设置

simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;

simp_blkdev_disk->first_minor = 0;

simp_blkdev_disk->fops =&simp_blkdev_fops;

simp_blkdev_disk->queue = simp_blkdev_queue;

set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);

add_disk(simp_blkdev_disk);

if (!simp_blkdev_disk) {

ret = -ENOMEM;

goto err_alloc_disk;

}

simp_blkdev_queue =blk_init_queue(simp_blkdev_do_request, NULL);//初始化请求队列

if (!simp_blkdev_queue) {

ret = -ENOMEM;

goto err_init_queue;

}//在加载模块时用simp_blkdev_do_request()函数的地址作参数

调用blk_init_queue()初始化一个请求队列

//用来从一个请求队列中拿出一条请求(其实严格来说,拿出的可能是请求中的一段)。

随后的处理请求本质上是根据rq_data_dir(req)返回的该请求的方向(读/写),把块设备中的数据装入req->buffer、或是把req->buffer中的数据写入块设备。

static voidsimp_blkdev_do_request(struct request_queue *q)//请求队列的处理函数。

{

struct request *req;

while ((req = elv_next_request(q)) != NULL) {

if ((req->sector +req->current_nr_sectors) << 9

> SIMP_BLKDEV_BYTES) {

printk(KERN_ERR SIMP_BLKDEV_DISKNAME

": bad request: block=%llu,count=%u\n",

(unsigned long long)req->sector,

req->current_nr_sectors);

end_request(req, 0);

continue;

}

switch (rq_data_dir(req)){

case READ:

memcpy(req->buffer,

simp_blkdev_data + (req->sector <<9),

req->current_nr_sectors << 9);

end_request(req, 1);

break;

case WRITE:

memcpy(simp_blkdev_data + (req->sector << 9),

req->buffer, req->current_nr_sectors<< 9);

end_request(req, 1);

break;

default:

/* No default because rq_data_dir(req) is 1 bit */

break;

}

}

}

return 0;

err_alloc_disk:

blk_cleanup_queue(simp_blkdev_queue);

err_init_queue:

return ret;}

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

static void __exit simp_blkdev_exit(void)

{

del_gendisk(simp_blkdev_disk);

put_disk(simp_blkdev_disk);

blk_cleanup_queue(simp_blkdev_queue);

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容