Linux系統(tǒng)編程筆記-文件IO

本文主要介紹了如下內(nèi)容:

  • C標(biāo)準(zhǔn)庫(kù)函數(shù)與系統(tǒng)函數(shù)的關(guān)系
  • 進(jìn)程控制塊
  • 文件描述符
  • 系統(tǒng)調(diào)用:open、close、read、write、lseek、fcntl和ioctl

先導(dǎo)概念

C標(biāo)準(zhǔn)庫(kù)函數(shù)與系統(tǒng)函數(shù)的關(guān)系

API層次如圖所示:


API層次

API調(diào)用順序

由上往下(用戶態(tài) -> 內(nèi)核態(tài))的順序依次是:

  • C標(biāo)準(zhǔn)庫(kù)函數(shù):調(diào)用系統(tǒng)庫(kù)函數(shù)(即 系統(tǒng)調(diào)用);
  • 系統(tǒng)調(diào)用:即操作系統(tǒng)的應(yīng)用層API,調(diào)用內(nèi)核層API;
  • 內(nèi)核層API: 調(diào)用具體的驅(qū)動(dòng)層API(在Linux中一般以sys_開(kāi)頭);
  • 驅(qū)動(dòng)層函數(shù):直接控制硬件設(shè)備。

以調(diào)用fwrite()函數(shù)將文件內(nèi)容顯示在終端為例,fwrite()函數(shù)將調(diào)用write()系統(tǒng)調(diào)用,而write()系統(tǒng)調(diào)用的實(shí)現(xiàn)則是調(diào)用內(nèi)核態(tài)的sys_write()函數(shù),由sys_write()來(lái)判斷具體調(diào)用哪個(gè)驅(qū)動(dòng)函數(shù)來(lái)訪問(wèn)硬件設(shè)備。

當(dāng)然,對(duì)于Linux操作系統(tǒng)而言,還多了一層VFS(virtual File System,虛擬文件系統(tǒng))層:


write()系統(tǒng)調(diào)用將來(lái)自用戶空間的數(shù)據(jù)流,首先通過(guò)VFS的通用系統(tǒng)調(diào)用,然后通過(guò)文件系統(tǒng)的特殊寫(xiě)法,最后寫(xiě)入物理介質(zhì)中。

各API在緩沖區(qū)上的不同之處

  • fopen():每打開(kāi)一個(gè)文件,都會(huì)對(duì)應(yīng)一個(gè)單獨(dú)的緩沖區(qū);
  • open():無(wú)緩沖區(qū);
  • sys_open:有緩沖區(qū),但是由所有打開(kāi)的文件共用。

關(guān)于緩沖區(qū)的刷新方式:

  • 刷新C標(biāo)準(zhǔn)緩沖區(qū)
  1. 緩沖區(qū)滿,自動(dòng)刷新;
  2. 手動(dòng)調(diào)用fflush()函數(shù)刷新;
  3. 使用fclose()函數(shù)關(guān)閉文件時(shí)刷新;
  4. 程序正常結(jié)束后緩沖區(qū)自動(dòng)刷新。
  • 刷新內(nèi)核緩沖區(qū)
    由一個(gè)守護(hù)進(jìn)程定時(shí)刷新。

PCB和文件描述符fd

PCB(process control block,進(jìn)程控制塊)在Linux源碼中的實(shí)現(xiàn)即task_struct結(jié)構(gòu)體,位于/include/linux/sched.h文件中。該結(jié)構(gòu)體在Linux中被稱為進(jìn)程描述符(process descriptor)。 其部分結(jié)構(gòu)如下(linux kernel 版本為4.4.36):

1380 struct task_struct {
1381     volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */
1382     void *stack;
1383     atomic_t usage;
1384     unsigned int flags; /* per process flags, defined below */
1385     unsigned int ptrace;
1386
1387 #ifdef CONFIG_SMP
1388     struct llist_node wake_entry;
1389     int on_cpu;
1390     unsigned int wakee_flips;
1391     unsigned long wakee_flip_decay_ts;
1392     struct task_struct *last_wakee;
1393
1394     int wake_cpu;
1395 #endif
1396     int on_rq;


1483    pid_t pid;
1484    pid_t tgid;

Linux內(nèi)核把進(jìn)程的列表存放在任務(wù)隊(duì)列(task list)中,該隊(duì)列是一個(gè)雙向循環(huán)鏈表,鏈表中的每一項(xiàng)都是一個(gè)task_struct結(jié)構(gòu)體。

在Linux內(nèi)核中,每一個(gè)進(jìn)程都有一個(gè)PCB來(lái)管理,每一個(gè)PCB中都有一個(gè)指向files_struct結(jié)構(gòu)體的指針:

1564 /* open file information */
1565     struct files_struct *files;

可以看到,task_struct結(jié)構(gòu)體中的files是個(gè)指針(充當(dāng)目錄項(xiàng)的角色),指向files_struct結(jié)構(gòu)體。而files_struct結(jié)構(gòu)體是一張文件描述符表(實(shí)際上就是一個(gè)整形數(shù)組,里面存放的是諸如012這樣的文件描述符,文件描述符即一些非負(fù)整數(shù)),這些文件描述符指向真正的設(shè)備文件,包括磁盤文件、顯示屏文件等所有文件。

文件描述符struct files_struct 源碼:
(位于linux-4.4.36/include/linux/fdtable.h中)

 43 /*
 44  * Open file table structure
 45  */
 46 struct files_struct {
 47   /*
 48    * read mostly part
 49    */
 50     atomic_t count;  /* 該結(jié)構(gòu)體的引用計(jì)數(shù) */
 51     bool resize_in_progress;
 52     wait_queue_head_t resize_wait;
 53
 54     struct fdtable __rcu *fdt;
 55     struct fdtable fdtab;
 56   /*
 57    * written part on a separate cache line in SMP
 58    */
 59     spinlock_t file_lock ____cacheline_aligned_in_smp;
 60     int next_fd;
 61     unsigned long close_on_exec_init[1];
 62     unsigned long open_fds_init[1];   
 63     unsigned long full_fds_bits_init[1];
 64     struct file __rcu * fd_array[NR_OPEN_DEFAULT]; /* 缺省的文件對(duì)象數(shù)組 */
 65 };

關(guān)系圖:

文件句柄關(guān)系

文件‘開(kāi)’ ‘關(guān)’ ‘讀’ ‘寫(xiě)’的系統(tǒng)接口

open()

功能:打開(kāi)或者創(chuàng)建(如果文件不存在)一個(gè)文件。

每打開(kāi)一個(gè)文件,操作系統(tǒng)內(nèi)核(kernel)就會(huì)在內(nèi)存中新建一個(gè)files_struct結(jié)構(gòu)體。

在同一個(gè)進(jìn)程中 多次打開(kāi)同一個(gè)文件,內(nèi)核也會(huì)在內(nèi)存中分別新建不同的files_struct結(jié)構(gòu)體(由不同的文件描述符映射)。因此,每次打開(kāi)的文件在使用完之后一定要及時(shí)關(guān)閉,否則可能會(huì)引起內(nèi)存泄漏。

聲明:

NAME
       open - open and possibly create a file

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

       int open(const char *pathname, int flags);
       int open(const char *pathname, int flags, mode_t mode);

返回值:

  • 成功:返回新分配的文件描述符;
  • 出錯(cuò):則返回-1,并設(shè)置errno;

close()

功能:關(guān)閉一個(gè)打開(kāi)的文件,一般與open()成對(duì)使用。

每調(diào)用一次close(fd),實(shí)際上是將該文件描述符fd所指向的files_struct結(jié)構(gòu)體中的引用計(jì)數(shù)count值減一。當(dāng)引用計(jì)數(shù)值減為0時(shí),操作系統(tǒng)內(nèi)核(kernel)才真正關(guān)閉該文件。

通過(guò)調(diào)用dup/dup2系統(tǒng)調(diào)用可使files_struct結(jié)構(gòu)體中的引用計(jì)數(shù)count值加一。具體是dup/dup2新生成一個(gè)文件描述符newfd,并使其指向舊文件描述符oldfd所指向的files_struct結(jié)構(gòu)體,即這兩個(gè)文件描述符共用一個(gè)files_struct結(jié)構(gòu)體。

聲明:

NAME
       close - close a file descriptor

SYNOPSIS
       #include <unistd.h>

       int close(int fd);

返回值:

  • 成功:返回0;
  • 出錯(cuò):則返回-1,并設(shè)置errno;

read()

功能: 從打開(kāi)的設(shè)備或文件中讀取數(shù)據(jù)。
聲明:

NAME
       read - read from a file descriptor

SYNOPSIS
       #include <unistd.h>

       ssize_t read(int fd, void *buf, size_t count);

返回值:

  • 成功:返回讀取的字節(jié)數(shù);
  • 出錯(cuò):則返回-1,并設(shè)置errno;
  • 如果在調(diào)read之前已到達(dá)文件末尾,則這次read返回0。

write()

功能:從內(nèi)存地址buf開(kāi)始,向打開(kāi)的文件寫(xiě)入count字節(jié)(byte)的數(shù)據(jù)。
聲明:

NAME
       write - write to a file descriptor

SYNOPSIS
       #include <unistd.h>

       ssize_t write(int fd, const void *buf, size_t count);

返回值:

  • 成功:返回寫(xiě)入的字節(jié)數(shù);
  • 出錯(cuò):返回-1,并設(shè)置errno。

注意:
在向常規(guī)文件進(jìn)行寫(xiě)操作時(shí),write函數(shù)的返回值通常等于請(qǐng)求寫(xiě)的字節(jié)數(shù)count,而向終端設(shè)備或網(wǎng)絡(luò)設(shè)備進(jìn)行寫(xiě)操作時(shí)則不一定。

Demo:mycp.c

程序功能描述:模仿cp命令,將一個(gè)文件中的內(nèi)容復(fù)制到一個(gè)新的文件之中。

code:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define SIZE    8192

int main(int argc, char *argv[])
{
    int fd_src, fd_des, len;
    char buf[SIZE];

    /* 參數(shù)輸入太少,不符合要求,打印命令使用提示信息并退出 */
    if (argc < 3) {
        printf("Usage: ./mycp src_file des_file\n");
        exit(1);
    }

    /* 打開(kāi)源文件 */
    fd_src = open(argv[1], O_RDONLY);
    if (fd_src == -1) {
        printf("Openning file %s failed...\n", argv[1]);
        exit(-1);
    } 

    /* 新建目標(biāo)文件 */
    fd_des = open(argv[2], O_CREAT | O_WRONLY | O_TRUNC, 0664);
    if (fd_des == -1) {
        printf("Creating file %s failed...\n", argv[2]);
        exit(-1);
    }

    /* 讀取源文件中的內(nèi)容,然后寫(xiě)入目標(biāo)文件之中 */
    while ( (len = read(fd_src, buf, sizeof(buf))) > 0 ) {
        write(fd_des, buf, len);
    }

    /* 關(guān)閉文件 */
    close(fd_src);
    close(fd_des);

    return 0;
}

test

slot@slot-ubt:~/test$ gcc mycp.c -o mycp
slot@slot-ubt:~/test$ cat aa
Hello, this is my cp cmd.
Welcome to use...
slot@slot-ubt:~/test$ cat bb
cat: bb: No such file or directory
slot@slot-ubt:~/test$ ./mycp aa bb
slot@slot-ubt:~/test$ cat bb
Hello, this is my cp cmd.
Welcome to use...
slot@slot-ubt:~/test$

lseek()

功能:移動(dòng)打開(kāi)的文件的讀寫(xiě)指針的位置。

每個(gè)打開(kāi)的文件都記錄著當(dāng)前讀寫(xiě)指針的位置,打開(kāi)文件時(shí)讀寫(xiě)位置是0,表示文件開(kāi)頭。通常,讀寫(xiě)多少個(gè)字節(jié),就會(huì)將讀寫(xiě)位置往后移動(dòng)多少個(gè)字節(jié)。但有一個(gè)例外,如果以O_APPEND(追加)方式打開(kāi),則每次寫(xiě)操作都會(huì)在文件末尾追加數(shù)據(jù),然后將讀寫(xiě)位置移到新的文件末尾。

聲明:

NAME
       lseek - reposition read/write file offset

SYNOPSIS
       #include <sys/types.h>
       #include <unistd.h>

       off_t lseek(int fd, off_t offset, int whence);

lseek的兩個(gè)"副作用"示例

demo1. 擴(kuò)展一個(gè)文件

注意
拓展一個(gè)文件,一定要有一個(gè)寫(xiě)操作。

code:extend_file.c
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    int fd;

    /* 新建一個(gè)名為abc的文件 */
    fd = open("abc", O_CREAT | O_RDWR);
    if (fd < 0) {
        perror("Opening file failed: ");
        exit(-1);
    }

    /* 將讀寫(xiě)指針移到文件末尾 */
    lseek(fd, 0x1000, SEEK_SET);
    /* 追加寫(xiě)一個(gè)字節(jié)到文件中去
     * string "a" will be translated to an addr
     * od -tcx see file abc
     */
    write(fd, "a", 1); 
    close(fd);

    return 0;
}

errno是個(gè)用戶態(tài)的全局變量,聲明在頭文件/usr/include/errno.h中 :

 45 #ifndef errno
 46 extern int errno;
 47 #endif

Linux下的錯(cuò)誤碼可以查閱文件:/usr/include/asm-generic/errno-base.h (部分展示如下):

  1 #ifndef _ASM_GENERIC_ERRNO_BASE_H
  2 #define _ASM_GENERIC_ERRNO_BASE_H
  3 
  4 #define EPERM        1  /* Operation not permitted */        
  5 #define ENOENT       2  /* No such file or directory */
  6 #define ESRCH        3  /* No such process */
  7 #define EINTR        4  /* Interrupted system call */
  8 #define EIO      5  /* I/O error */
  9 #define ENXIO        6  /* No such device or address */
 10 #define E2BIG        7  /* Argument list too long */
 11 #define ENOEXEC      8  /* Exec format error */
 12 #define EBADF        9  /* Bad file number */
 13 #define ECHILD      10  /* No child processes */
 14 #define EAGAIN      11  /* Try again */
 15 #define ENOMEM      12  /* Out of memory */
 16 #define EACCES      13  /* Permission denied */

perror()函數(shù)將打印用戶自定義信息和errno后面對(duì)應(yīng)的注釋信息,其聲明為:

NAME
       perror - print a system error message
>
SYNOPSIS
       #include <stdio.h>
>
       void perror(const char *s);
>
       #include <errno.h>
>
       const char * const sys_errlist[];
       int sys_nerr;
       int  errno;        
test:
slot@slot-ubt:~/test$ gcc extend_file.c -o exf
slot@slot-ubt:~/test$ ./exf
slot@slot-ubt:~/test$ od -txc abc
0000000          00000000        00000000        00000000        00000000
          \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0010000          00000061
           a
0010001

demo2. 獲取文件的大小

方法:將指針移到文件末尾,然后輸出返回值,該值即文件大小。

code: see_file_size.c
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    int fd;

    fd = open("abc", O_RDWR);
    if (fd < 0) {
        perror("Opening file failed: ");
        exit(-1);
    }

    /* print file size */ 
    printf("abc size is: %lld\n", lseek(fd, 0, SEEK_END));
     
    close(fd);

    return 0;
}

test:
slot@slot-ubt:~/test$ gcc see_file_size.c -o fsize
slot@slot-ubt:~/test$ ./fsize
abc size is: 4097
slot@slot-ubt:~/test$ ls -l abc
-rwxrwxrwx  1 slot  staff  4097 12 14 19:40 abc

fcntl()

功能: 獲取或者設(shè)置已打開(kāi)文件的訪問(wèn)屬性。
聲明:

NAME
       fcntl - manipulate file descriptor

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

       int fcntl(int fd, int cmd, ... /* arg */ );

demo:

改變文件的狀態(tài)標(biāo)志位為非阻塞狀態(tài)

code: test_fcntl.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

int main()
{
    char buf[10];
    int n;
    int flags;

    /* get file flag */
    flags = fcntl(STDIN_FILENO, F_GETFL);
    
    /* change file flags to nonblock */
    flags |= O_NONBLOCK;
    if (fcntl(STDIN_FILENO, F_SETTL, flags) == -1) {
        perror("change file flag failed: ");
        exit(1);
    }

try_again:
    n = read(STDIN_FILENO, buf, 10);
    if (n < 0) {
        if (errno == EAGAIN) {
            sleep(1);
            write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
            goto try_again;
        }
        perror("read stdin failed: ");
        exit(1);
    }
    write(STDOUT_FILENO, buf, n);

    return 0;
}

test:


ioctl()

功能:向設(shè)備發(fā)送控制和配置命令。

有些命令也需要讀寫(xiě)一些數(shù)據(jù),但這些數(shù)據(jù)是不能用read/write讀寫(xiě)的,稱為Out-of-band數(shù)據(jù)。也就是說(shuō),read/write讀寫(xiě)的數(shù)據(jù)是in-band數(shù)據(jù),是I/O操作的主體,而ioctl命令傳送的是控制信息,其中的數(shù)據(jù)是輔助的數(shù)據(jù)。

例如,在串口線上收發(fā)數(shù)據(jù)通過(guò)read/write操作,而串口的波特率、校驗(yàn)位、停止位則通過(guò)ioctl來(lái)設(shè)置;A/D轉(zhuǎn)換(模數(shù)轉(zhuǎn)換)的結(jié)果通過(guò)read讀取,而A/D轉(zhuǎn)換的精度和工作頻率則通過(guò)ioctl設(shè)置。

聲明:

NAME
       ioctl - control device

SYNOPSIS
       #include <sys/ioctl.h>

       int ioctl(int fd, unsigned long request, ...);

fd是某個(gè)設(shè)備的文件描述符,request是ioctl的命令,可變參數(shù)取決于request,通常是一個(gè)指向變量或結(jié)構(gòu)體的指針。

若出錯(cuò),則返回-1;若成功,則返回其他值。返回值也取決于request

demo: 獲取終端窗口的大小

code: get_tty_size.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>

int main(void)
{
    struct winsize size;
    
    /* 不是終端設(shè)備文件則退出 */
    if (isatty(STDOUT_FILENO) == 0)
        exit(1);

    /* 通過(guò) ioctl() 獲取終端窗口的大小 */
    if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) < 0) {
        perror("ioctl TIOCGWINSZ error");
        exit(1);
    } 
    /* 打印終端窗口的長(zhǎng)和寬 */
    printf("%d rows, %d columns\n", size.ws_row, size.ws_col);

    return 0;
}


test:

(測(cè)試結(jié)果依賴于當(dāng)前終端的窗口大小)

slot@slot-ubt:~/test$ gcc get_tty_size.c -o ttysize
slot@slot-ubt:~/test$ 
slot@slot-ubt:~/test$ ./ttysize 
24 rows, 65 columns
slot@slot-ubt:~/test$ 
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,656評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,647評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,951評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,189評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,419評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評(píng)論 2 371

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