Linux字符驅(qū)動

本篇簡單介紹如何寫一個簡單的字符設(shè)備驅(qū)動,實現(xiàn)一個與硬件設(shè)備無關(guān)的字符設(shè)備驅(qū)動爸邢,僅僅操作從內(nèi)核中分配的一些內(nèi)存科乎。
下面就開始學(xué)習(xí)如何寫一個簡單的字符設(shè)備驅(qū)動密强。首先我們來分解一下字符設(shè)備驅(qū)動都有那些結(jié)構(gòu)或者方法組成宴杀,也就是說實現(xiàn)一個可以使用的字符設(shè)備驅(qū)動我們必須做些什么工作癣朗。

1、主設(shè)備號和次設(shè)備號

對于字符設(shè)備的訪問是通過文件系統(tǒng)中的設(shè)備名稱進行的旺罢。他們通常位于/dev目錄下旷余。如下:

xxx@ubuntu:~$ ls -l /dev/  
total 0  
brw-rw----  1 root disk        7,   0  3月 25 10:34 loop0  
brw-rw----  1 root disk        7,   1  3月 25 10:34 loop1  
brw-rw----  1 root disk        7,   2  3月 25 10:34 loop2  
crw-rw-rw-  1 root tty         5,   0  3月 25 12:48 tty  
crw--w----  1 root tty         4,   0  3月 25 10:34 tty0  
crw-rw----  1 root tty         4,   1  3月 25 10:34 tty1  
crw--w----  1 root tty         4,  10  3月 25 10:34 tty10  

其中b代表塊設(shè)備,c代表字符設(shè)備扁达。對于普通文件來說正卧,ls -l會列出文件的長度,而對于設(shè)備文件來說跪解,上面的7,5,4等代表的是對應(yīng)設(shè)備的主設(shè)備號炉旷,而后面的0,1,2,10等則是對應(yīng)設(shè)備的次設(shè)備號。那么主設(shè)備號和次設(shè)備號分別代表什么意義呢?一般情況下窘行,可以這樣理解骏啰,主設(shè)備號標(biāo)識設(shè)備對應(yīng)的驅(qū)動程序,也就是說1個主設(shè)備號對應(yīng)一個驅(qū)動程序抽高。當(dāng)然判耕,現(xiàn)在也有多個驅(qū)動程序共享主設(shè)備號的情況。而次設(shè)備號有內(nèi)核使用翘骂,用于確定/dev下的設(shè)備文件對應(yīng)的具體設(shè)備壁熄。舉一個例子,虛擬控制臺和串口終端有驅(qū)動程序4管理碳竟,而不同的終端分別有不同的次設(shè)備號草丧。

1.1、設(shè)備編號的表達(dá)

在內(nèi)核中莹桅,dev_t用來保存設(shè)備編號昌执,包括主設(shè)備號和次設(shè)備號。在2.6的內(nèi)核版本種诈泼,dev_t是一個32位的數(shù)懂拾,其中12位用來表示主設(shè)備號,其余20位用來標(biāo)識次設(shè)備號铐达。
通過dev_t獲取主設(shè)備號和次設(shè)備號使用下面的宏:

MAJOR(dev_t dev);
MINOR(dev_t dev);

相反岖赋,通過主設(shè)備號和次設(shè)備號轉(zhuǎn)換為dev_t類型使用:

MKDEV(int major, int minor);
1.2、分配和釋放設(shè)備編號

在構(gòu)建一個字符設(shè)備之前瓮孙,驅(qū)動程序首先要獲得一個或者多個設(shè)備編號唐断,這類似一個營業(yè)執(zhí)照,有了營業(yè)執(zhí)照才在內(nèi)核中正常工作營業(yè)杭抠。完成此工作的函數(shù)是:

int register_chrdev_region(dev_t first, unsigned int count, const char *name);  

first是要分配的設(shè)備編號范圍的起始值脸甘。count是連續(xù)設(shè)備的編號的個數(shù)。name是和該設(shè)備編號范圍關(guān)聯(lián)的設(shè)備名稱偏灿,他將出現(xiàn)在/proc/devices和sysfs中丹诀。此函數(shù)成功返回0,失敗返回負(fù)的錯誤碼菩混。此函數(shù)是在已知主設(shè)備號的情況下使用忿墅,在未知主設(shè)備號的情況下,我們使用下面的函數(shù):

int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);  

dev用于輸出申請到的設(shè)備編號沮峡,firstminor要使用的第一個此設(shè)備編號。
在不使用時需要釋放這些設(shè)備編號亿柑,已提供其他設(shè)備程序使用:

void unregister_chrdev_region(dev_t dev, unsigned int count);  

此函數(shù)多在模塊的清除函數(shù)中調(diào)用邢疙。
分配到設(shè)備編號之后,我們只是拿到了營業(yè)執(zhí)照,雖說現(xiàn)在已經(jīng)準(zhǔn)備的差不多了,但是我們只是從內(nèi)核中申請到了設(shè)備號,應(yīng)用程序還是不能對此設(shè)備作任何事情,我們需要一個簡單的函數(shù)來把設(shè)備編號和此設(shè)備能實現(xiàn)的功能連接起來,這樣我們的模塊才能提供具體的功能.這個操作很簡單疟游,稍后就會提到呼畸,在此之前先介紹幾個重要的數(shù)據(jù)結(jié)構(gòu)。

2颁虐、重要的數(shù)據(jù)結(jié)構(gòu)

注冊設(shè)備編號僅僅是完成一個字符設(shè)備驅(qū)動的第一步蛮原。下面介紹大部分驅(qū)動都會包含的三個重要的內(nèi)核的數(shù)據(jù)結(jié)構(gòu)。

2.1另绩、文件操作file_operations

file_operations是第一個重要的結(jié)構(gòu)儒陨,定義在 <linux/fs.h>, 是一個函數(shù)指針的集合,設(shè)備所能提供的功能大部分都由此結(jié)構(gòu)提供笋籽。這些操作也是設(shè)備相關(guān)的系統(tǒng)調(diào)用的具體實現(xiàn)蹦漠。此結(jié)構(gòu)的具體實現(xiàn)如下所示:

struct file_operations {  
        //它是一個指向擁有這個結(jié)構(gòu)的模塊的指針. 這個成員用來在它的操作還在被使用時阻止模塊被卸載. 幾乎所有時間中, 它被簡單初始化為 THIS_MODULE  
        struct module *owner;  
        loff_t (*llseek) (struct file *, loff_t, int);  
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);  
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);  
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);  
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);  
        int (*iterate) (struct file *, struct dir_context *);  
        unsigned int (*poll) (struct file *, struct poll_table_struct *);  
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);  
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);  
        int (*mmap) (struct file *, struct vm_area_struct *);  
        int (*open) (struct inode *, struct file *);  
        int (*flush) (struct file *, fl_owner_t id);  
        int (*release) (struct inode *, struct file *);  
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);  
        int (*aio_fsync) (struct kiocb *, int datasync);  
        int (*fasync) (int, struct file *, int);  
        int (*lock) (struct file *, int, struct file_lock *);  
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);  
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  
        int (*check_flags)(int);  
        int (*flock) (struct file *, int, struct file_lock *);  
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);  
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);  
        int (*setlease)(struct file *, long, struct file_lock **);  
        long (*fallocate)(struct file *file, int mode, loff_t offset,  
                          loff_t len);  
        int (*show_fdinfo)(struct seq_file *m, struct file *f);  
};  

需要說明的是這里面的函數(shù)在驅(qū)動中不用全部實現(xiàn),不支持的操作留置為NULL车海。

2.2笛园、文件結(jié)構(gòu)struct file

struct file, 定義于 <linux/fs.h>, 是設(shè)備驅(qū)動中第二個最重要的數(shù)據(jù)結(jié)構(gòu)。文件結(jié)構(gòu)代表一個打開的文件. (它不特定給設(shè)備驅(qū)動; 系統(tǒng)中每個打開的文件有一個關(guān)聯(lián)的 struct file 在內(nèi)核空間). 它由內(nèi)核在 open 時創(chuàng)建, 并傳遞給在文件上操作的任何函數(shù), 直到最后的關(guān)閉. 在文件的所有實例都關(guān)閉后, 內(nèi)核釋放這個數(shù)據(jù)結(jié)構(gòu)侍芝。file結(jié)構(gòu)的詳細(xì)可參考fs.h研铆,這里列出來幾個重要的成員。

  • struct file_operations *** f_op:就是上面剛剛介紹的文件操作的集合結(jié)構(gòu)州叠。
  • mode_t f_mode:文件模式確定文件是可讀的或者是可寫的(或者都是), 通過位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函數(shù)中檢查這個成員的讀寫許可, 但是你不需要檢查讀寫許可, 因為內(nèi)核在調(diào)用你的方法之前檢查. 當(dāng)文件還沒有為那種存取而打開時讀或?qū)懙钠髨D被拒絕, 驅(qū)動甚至不知道這個情況
  • loff_t f_pos:當(dāng)前讀寫位置. loff_t 在所有平臺都是 64 位蚜印。驅(qū)動可以讀這個值, 如果它需要知道文件中的當(dāng)前位置, 但是正常地不應(yīng)該改變它。
  • unsigned int f_flags:這些是文件標(biāo)志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驅(qū)動應(yīng)當(dāng)檢查 O_NONBLOCK 標(biāo)志來看是否是請求非阻塞操作留量。
  • void *private_data:open 系統(tǒng)調(diào)用設(shè)置這個指針為 NULL, 在為驅(qū)動調(diào)用 open 方法之前. 你可自由使用這個成員或者忽略它; 你可以使用這個成員來指向分配的數(shù)據(jù), 但是接著你必須記住在內(nèi)核銷毀文件結(jié)構(gòu)之前, 在 release 方法中釋放那個內(nèi)存. private_data 是一個有用的資源, 在系統(tǒng)調(diào)用間保留狀態(tài)信息, 我們大部分例子模塊都使用它
2.3窄赋、inode 結(jié)構(gòu)

inode 結(jié)構(gòu)由內(nèi)核在內(nèi)部用來表示文件. 因此, 它和代表打開文件描述符的文件結(jié)構(gòu)是不同的. 可能有代表單個文件的多個打開描述符的許多文件結(jié)構(gòu), 但是它們都指向一個單個 inode 結(jié)構(gòu)。
inode 結(jié)構(gòu)包含大量關(guān)于文件的信息楼熄。但對于驅(qū)動程序編寫來說一般不用關(guān)心忆绰,暫且不說。

3可岂、字符設(shè)備的注冊

內(nèi)核在內(nèi)部使用類型 struct cdev 的結(jié)構(gòu)來代表字符設(shè)備. 在內(nèi)核調(diào)用你的設(shè)備操作前, 你編寫分配并注冊一個或幾個這些結(jié)構(gòu)错敢。有 2 種方法來分配和初始化一個這些結(jié)構(gòu). 如果你想在運行時獲得一個獨立的 cdev 結(jié)構(gòu), 你可以為此使用這樣的代碼:

struct cdev *my_cdev = cdev_alloc();  
my_cdev->ops = &my_fops;  

更多的情況是把cdv結(jié)構(gòu)嵌入到你自己封裝的設(shè)備結(jié)構(gòu)中,這時需要使用下面的方法來分配和初始化:

void cdev_init(struct cdev *cdev, struct file_operations *fops);  

后面的例子程序就是這么做的缕粹。一旦 cdev 結(jié)構(gòu)建立, 最后的步驟是把它告訴內(nèi)核:

int cdev_add(struct cdev *dev, dev_t num, unsigned int count)  

這里, dev 是 cdev 結(jié)構(gòu), num 是這個設(shè)備響應(yīng)的第一個設(shè)備號, count 是應(yīng)當(dāng)關(guān)聯(lián)到設(shè)備的設(shè)備號的數(shù)目. 常常 count 是 1稚茅。
從系統(tǒng)去除一個字符設(shè)備, 調(diào)用:

void cdev_del(struct cdev *dev);  

4、一個簡單的字符設(shè)備

上面大致介紹了實現(xiàn)一個字符設(shè)備所要做的工作平斩,下面就來一個真實的例子來總結(jié)上面介紹的內(nèi)容亚享。源碼中的關(guān)鍵地方已經(jīng)作了注釋。

#include <linux/module.h>  
#include <linux/types.h>  
#include <linux/fs.h>  
#include <linux/errno.h>  
#include <linux/mm.h>  
#include <linux/sched.h>  
#include <linux/init.h>  
#include <linux/cdev.h>  
#include <asm/io.h>  
#include <asm/uaccess.h>  
#include <linux/timer.h>  
#include <asm/atomic.h>  
#include <linux/slab.h>  
#include <linux/device.h>  
  
#define CDEVDEMO_MAJOR 255  /*預(yù)設(shè)cdevdemo的主設(shè)備號*/  
  
static int cdevdemo_major = CDEVDEMO_MAJOR;  
  
/*設(shè)備結(jié)構(gòu)體,此結(jié)構(gòu)體可以封裝設(shè)備相關(guān)的一些信息等 
  信號量等也可以封裝在此結(jié)構(gòu)中绘面,后續(xù)的設(shè)備模塊一般都 
  應(yīng)該封裝一個這樣的結(jié)構(gòu)體欺税,但此結(jié)構(gòu)體中必須包含某些 
  成員侈沪,對于字符設(shè)備來說,我們必須包含struct cdev cdev*/  
struct cdevdemo_dev   
{  
    struct cdev cdev;  
};  
  
struct cdevdemo_dev *cdevdemo_devp; /*設(shè)備結(jié)構(gòu)體指針*/  
  
/*文件打開函數(shù)晚凿,上層對此設(shè)備調(diào)用open時會執(zhí)行*/  
int cdevdemo_open(struct inode *inode, struct file *filp)     
{  
    printk(KERN_NOTICE "======== cdevdemo_open ");  
    return 0;  
}  
  
/*文件釋放亭罪,上層對此設(shè)備調(diào)用close時會執(zhí)行*/  
int cdevdemo_release(struct inode *inode, struct file *filp)      
{  
    printk(KERN_NOTICE "======== cdevdemo_release ");     
    return 0;  
}  
  
/*文件的讀操作,上層對此設(shè)備調(diào)用read時會執(zhí)行*/  
static ssize_t cdevdemo_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos)  
{  
    printk(KERN_NOTICE "======== cdevdemo_read ");    
}  
  
/* 文件操作結(jié)構(gòu)體歼秽,文中已經(jīng)講過這個結(jié)構(gòu)*/  
static const struct file_operations cdevdemo_fops =  
{  
    .owner = THIS_MODULE,  
    .open = cdevdemo_open,  
    .release = cdevdemo_release,  
    .read = cdevdemo_read,  
};  
  
/*初始化并注冊cdev*/  
static void cdevdemo_setup_cdev(struct cdevdemo_dev *dev, int index)  
{  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 1");     
    int err, devno = MKDEV(cdevdemo_major, index);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 2");  
  
    /*初始化一個字符設(shè)備应役,設(shè)備所支持的操作在cdevdemo_fops中*/     
    cdev_init(&dev->cdev, &cdevdemo_fops);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 3");     
    dev->cdev.owner = THIS_MODULE;  
    dev->cdev.ops = &cdevdemo_fops;  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 4");     
    err = cdev_add(&dev->cdev, devno, 1);  
    printk(KERN_NOTICE "======== cdevdemo_setup_cdev 5");  
    if(err)  
    {  
        printk(KERN_NOTICE "Error %d add cdevdemo %d", err, index);   
    }  
}  
  
int cdevdemo_init(void)  
{  
    printk(KERN_NOTICE "======== cdevdemo_init ");    
    int ret;  
    dev_t devno = MKDEV(cdevdemo_major, 0);  
  
    struct class *cdevdemo_class;  
    /*申請設(shè)備號,如果申請失敗采用動態(tài)申請方式*/  
    if(cdevdemo_major)  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 1");  
        ret = register_chrdev_region(devno, 1, "cdevdemo");  
    }else  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 2");  
        ret = alloc_chrdev_region(&devno,0,1,"cdevdemo");  
        cdevdemo_major = MAJOR(devno);  
    }  
    if(ret < 0)  
    {  
        printk(KERN_NOTICE "======== cdevdemo_init 3");  
        return ret;  
    }  
    /*動態(tài)申請設(shè)備結(jié)構(gòu)體內(nèi)存*/  
    cdevdemo_devp = kmalloc(sizeof(struct cdevdemo_dev), GFP_KERNEL);  
    if(!cdevdemo_devp)  /*申請失敗*/  
    {  
        ret = -ENOMEM;  
        printk(KERN_NOTICE "Error add cdevdemo");     
        goto fail_malloc;  
    }  
  
    memset(cdevdemo_devp,0,sizeof(struct cdevdemo_dev));  
    printk(KERN_NOTICE "======== cdevdemo_init 3");  
    cdevdemo_setup_cdev(cdevdemo_devp, 0);  
  
    /*下面兩行是創(chuàng)建了一個總線類型燥筷,會在/sys/class下生成cdevdemo目錄 
      這里的還有一個主要作用是執(zhí)行device_create后會在/dev/下自動生成 
      cdevdemo設(shè)備節(jié)點箩祥。而如果不調(diào)用此函數(shù),如果想通過設(shè)備節(jié)點訪問設(shè)備 
      需要手動mknod來創(chuàng)建設(shè)備節(jié)點后再訪問荆责。*/  
    cdevdemo_class = class_create(THIS_MODULE, "cdevdemo");  
    device_create(cdevdemo_class, NULL, MKDEV(cdevdemo_major, 0), NULL, "cdevdemo");  
  
    printk(KERN_NOTICE "======== cdevdemo_init 4");  
    return 0;  
  
    fail_malloc:  
        unregister_chrdev_region(devno,1);  
}  
  
void cdevdemo_exit(void)    /*模塊卸載*/  
{  
    printk(KERN_NOTICE "End cdevdemo");   
    cdev_del(&cdevdemo_devp->cdev);  /*注銷cdev*/  
    kfree(cdevdemo_devp);       /*釋放設(shè)備結(jié)構(gòu)體內(nèi)存*/  
    unregister_chrdev_region(MKDEV(cdevdemo_major,0),1);    //釋放設(shè)備號  
}  
  
MODULE_LICENSE("Dual BSD/GPL");  
module_param(cdevdemo_major, int, S_IRUGO);  
module_init(cdevdemo_init);  
module_exit(cdevdemo_exit);  

Makefile文件如下:

ifneq ($(KERNELRELEASE),)  
obj-m := cdevdemo.o  
else  
KERNELDIR ?= /lib/modules/$(shell uname -r)/build  
PWD := $(shell pwd)  
default:  
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
endif  
  
clean:  
    rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers  

溫馨提示:測試環(huán)境為Linux ubuntu 3.16.0-33-generic滥比。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市做院,隨后出現(xiàn)的幾起案子盲泛,更是在濱河造成了極大的恐慌,老刑警劉巖键耕,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寺滚,死亡現(xiàn)場離奇詭異,居然都是意外死亡屈雄,警方通過查閱死者的電腦和手機村视,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來酒奶,“玉大人蚁孔,你說我怎么就攤上這事⊥锖浚” “怎么了杠氢?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長另伍。 經(jīng)常有香客問我鼻百,道長,這世上最難降的妖魔是什么摆尝? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任温艇,我火速辦了婚禮,結(jié)果婚禮上堕汞,老公的妹妹穿的比我還像新娘勺爱。我一直安慰自己,他們只是感情好臼朗,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布邻寿。 她就那樣靜靜地躺著蝎土,像睡著了一般视哑。 火紅的嫁衣襯著肌膚如雪绣否。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天挡毅,我揣著相機與錄音蒜撮,去河邊找鬼。 笑死跪呈,一個胖子當(dāng)著我的面吹牛段磨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播耗绿,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼苹支,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了误阻?” 一聲冷哼從身側(cè)響起债蜜,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎究反,沒想到半個月后寻定,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡精耐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年狼速,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卦停。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡向胡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惊完,到底是詐尸還是另有隱情僵芹,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布专执,位于F島的核電站淮捆,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏本股。R本人自食惡果不足惜攀痊,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拄显。 院中可真熱鬧苟径,春花似錦、人聲如沸躬审。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遭殉,卻和暖如春石挂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背险污。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工痹愚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蛔糯。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓拯腮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蚁飒。 傳聞我的和親對象是個殘疾皇子动壤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容