Android 日誌系統logcat內核代碼分析

http://blog.csdn.net/andyhuabing/article/details/8547875

前一篇文章:http://blog.csdn.net/andyhuabing/article/details/8547719 簡要介紹了log系統的上層使用方法,本文重點分
析其log內核驅動代碼,使得我們對Android日誌系統有一個深刻的認識。


內核代碼路徑:

kernel/drivers/staging/android/logger.h
kernel/drivers/staging/android/logger.c


1、Logger驅動程序的相關數據結構

首先來看logger.h頭文件的內容:

  1. /* include/linux/logger.h 
  2.  * 
  3.  * Copyright (C) 2007-2008 Google, Inc. 
  4.  * Author: Robert Love <[email protected]> 
  5.  * 
  6.  * This software is licensed under the terms of the GNU General Public 
  7.  * License version 2, as published by the Free Software Foundation, and 
  8.  * may be copied, distributed, and modified under those terms. 
  9.  * 
  10.  * This program is distributed in the hope that it will be useful, 
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13.  * GNU General Public License for more details. 
  14.  * 
  15.  */  
  16.   
  17. #ifndef _LINUX_LOGGER_H  
  18. #define _LINUX_LOGGER_H  
  19.   
  20. #include <linux/types.h>  
  21. #include <linux/ioctl.h>  
  22.   
  23. struct logger_entry {  
  24.     __u16       len;    /* length of the payload */  
  25.     __u16       __pad;  /* no matter what, we get 2 bytes of padding */  
  26.     __s32       pid;    /* generating process's pid */  
  27.     __s32       tid;    /* generating process's tid */  
  28.     __s32       sec;    /* seconds since Epoch */  
  29.     __s32       nsec;   /* nanoseconds */  
  30.     char        msg[0]; /* the entry's payload */  
  31. };  
  32.   
  33. #define LOGGER_LOG_RADIO    "log_radio" /* radio-related messages */  
  34. #define LOGGER_LOG_EVENTS   "log_events"    /* system/hardware events */  
  35. #define LOGGER_LOG_SYSTEM   "log_system"    /* system/framework messages */  
  36. #define LOGGER_LOG_MAIN     "log_main"  /* everything else */  
  37.   
  38. #define LOGGER_ENTRY_MAX_LEN        (4*1024)  
  39. #define LOGGER_ENTRY_MAX_PAYLOAD    \  
  40.     (LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))  
  41.   
  42. #define __LOGGERIO  0xAE  
  43.   
  44. #define LOGGER_GET_LOG_BUF_SIZE     _IO(__LOGGERIO, 1) /* size of log */  
  45. #define LOGGER_GET_LOG_LEN      _IO(__LOGGERIO, 2) /* used log len */  
  46. #define LOGGER_GET_NEXT_ENTRY_LEN   _IO(__LOGGERIO, 3) /* next entry len */  
  47. #define LOGGER_FLUSH_LOG        _IO(__LOGGERIO, 4) /* flush log */  
  48.   
  49. #endif /* _LINUX_LOGGER_H */  

struct logger_entry是一個用於描述一條Log記錄的結構體。
其中len成員變量記錄了這條記錄的有效負載的長度,有效負載指定的日誌記錄本身的長度,但是不包括用於描述這個記錄的struct logger_entry結構體。

從struct logger_entry中也可以看出:優先級別Priority、Tag字符串以及Msg字符串,pid和tid成員變量分別用來記錄是哪條進程寫入了這條記錄。sec和nsec成員變量記錄日誌寫的時間。msg成員變量記錄的就有效負載的內容了,它的大小由len成員變量來確定


#define LOGGER_ENTRY_MAX_LEN(4*1024)
#define LOGGER_ENTRY_MAX_PAYLOAD \
(LOGGER_ENTRY_MAX_LEN - sizeof(struct logger_entry))

這兩個宏定義記錄了 最大有效負載長度。


再分析下logger.c實現文件:

  1. /* 
  2.  * struct logger_log - represents a specific log, such as 'main' or 'radio' 
  3.  * 
  4.  * This structure lives from module insertion until module removal, so it does 
  5.  * not need additional reference counting. The structure is protected by the 
  6.  * mutex 'mutex'. 
  7.  */  
  8. struct logger_log {  
  9.     unsigned char       *buffer;/* the ring buffer itself */  
  10.     struct miscdevice   misc;   /* misc device representing the log */  
  11.     wait_queue_head_t   wq; /* wait queue for readers */  
  12.     struct list_head    readers; /* this log's readers */  
  13.     struct mutex        mutex;  /* mutex protecting buffer */  
  14.     size_t          w_off;  /* current write head offset */  
  15.     size_t          head;   /* new readers start here */  
  16.     size_t          size;   /* size of the log */  
  17. };  

  結構體struct logger_log就是真正用來保存日誌的地方了。buffer成員變量變是用保存日誌信息的內存緩衝區,它的大小由size成員變量確定。

 buffer是一個循環使用的環形緩衝區,緩衝區中保存的內容是以struct logger_entry爲單位的,其組成方式是:

struct logger_entry | priority | tag | msg

/*

  1. struct logger_reader - a logging device open for reading  
  2. *  
  3. * This object lives from open to release, so we don't need additional  
  4. * reference counting. The structure is protected by log->mutex.  
  5. */  
  6. truct logger_reader {  
  7. struct logger_log   *log;   /* associated log */  
  8. struct list_head    list;   /* entry in logger_log's list */  
  9. size_t          r_off;  /* current read head offset */  
  10. ;  

 結構體struct logger_reader用來表示一個讀取日誌的進程,log成員變量指向要讀取的日誌緩衝區。list成員變量用來連接其它讀者進程。r_off成員變量表示當前要讀取的日誌在緩衝區中的位置。


2、模塊初始化過程:

logger是一個misc設備,那麼misc設備是個什麼東東呢?網上有很多資料,這裏簡要說明一下:

雜設備——misc

簡單的說,雜設備就是內核自動幫你分配設備號並且自動創建設備文件。

1、自動分配設備號,是指所有註冊爲雜設備的設備的主設備號爲10,而次設備號內核自動分配。

2、自動創建設備文件是指,內核會使用udev(前提是你已經移植udev),動態創建設備節點。


利用:

int misc_register(struct miscdevice * misc); //註冊

int misc_deregister(struct miscdevice *misc); //註銷


執行cat /proc/devices命令可以查看此類設備

# cat /proc/devices

Character devices:

1 mem

4 /dev/vc/0

4 tty

5 /dev/tty

5 /dev/console

5 /dev/ptmx

10 misc //創建的設備在misc


定義了三個日誌設備:

  1. /* 
  2.  * Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which 
  3.  * must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than 
  4.  * LONG_MAX minus LOGGER_ENTRY_MAX_LEN. 
  5.  */  
  6. #define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \  
  7. static unsigned char _buf_ ## VAR[SIZE]; \  
  8. static struct logger_log VAR = { \  
  9.     .buffer = _buf_ ## VAR, \  
  10.     .misc = { \  
  11.         .minor = MISC_DYNAMIC_MINOR, \  
  12.         .name = NAME, \  
  13.         .fops = &logger_fops, \  
  14.         .parent = NULL, \  
  15.     }, \  
  16.     .wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \  
  17.     .readers = LIST_HEAD_INIT(VAR .readers), \  
  18.     .mutex = __MUTEX_INITIALIZER(VAR .mutex), \  
  19.     .w_off = 0, \  
  20.     .head = 0, \  
  21.     .size = SIZE, \  
  22. };  
  23.   
  24. DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)  
  25. DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)  
  26. DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024)  
  27. DEFINE_LOGGER_DEVICE(log_system, LOGGER_LOG_SYSTEM, 64*1024)  

分別是log_main、log_events和log_radio,名稱分別LOGGER_LOG_MAIN、LOGGER_LOG_EVENTS和LOGGER_LOG_RADIO

這三個不同名稱的設備文件操作方法如下:

  1. static const struct file_operations logger_fops = {  
  2.     .owner = THIS_MODULE,  
  3.     .read = logger_read,  
  4.     .aio_write = logger_aio_write,  
  5.     .poll = logger_poll,  
  6.     .unlocked_ioctl = logger_ioctl,  
  7.     .compat_ioctl = logger_ioctl,  
  8.     .open = logger_open,  
  9.     .release = logger_release,  
  10. };  

日誌驅動程序模塊的初始化函數爲logger_init:

  1. static int __init init_log(struct logger_log *log)  
  2. {  
  3.     int ret;  
  4.   
  5.     ret = misc_register(&log->misc);  
  6.     if (unlikely(ret)) {  
  7.         printk(KERN_ERR "logger: failed to register misc "  
  8.                "device for log '%s'!\n", log->misc.name);  
  9.         return ret;  
  10.     }  
  11.   
  12.     printk(KERN_INFO "logger: created %luK log '%s'\n",  
  13.            (unsigned long) log->size >> 10, log->misc.name);  
  14.   
  15.     return 0;  
  16. }  
  17.   
  18. static int __init logger_init(void)  
  19. {  
  20.     int ret;  
  21.   
  22.     ret = init_log(&log_main);  
  23.     if (unlikely(ret))  
  24.         goto out;  
  25.   
  26.     ret = init_log(&log_events);  
  27.     if (unlikely(ret))  
  28.         goto out;  
  29.   
  30.     ret = init_log(&log_radio);  
  31.     if (unlikely(ret))  
  32.         goto out;  
  33.   
  34.     ret = init_log(&log_system);  
  35.     if (unlikely(ret))  
  36.         goto out;  
  37.   
  38. out:  
  39.     return ret;  
  40. }  
  41. device_initcall(logger_init);  

logger_init函數通過調用init_log函數來初始化了上述提到的三個日誌設備,而init_log函數主要調用了misc_register函數來註冊misc設備。


3、日誌寫入重要過程分析:

註冊的寫入日誌設備文件的方法爲logger_aio_write

  1. <span style="color:#333333;">/* 
  2.  * logger_aio_write - our write method, implementing support for write(), 
  3.  * writev(), and aio_write(). Writes are our fast path, and we try to optimize 
  4.  * them above all else. 
  5.  */  
  6. ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,  
  7.              unsigned long nr_segs, loff_t ppos)  
  8. {  
  9.     struct logger_log *log = file_get_log(iocb->ki_filp);  
  10.     size_t orig = log->w_off;  
  11.     </span><span style="color:#cc0000;">struct logger_entry header</span><span style="color:#333333;">;  
  12.     struct timespec now;  
  13.     ssize_t ret = 0;  
  14. </span><span style="color:#ff6600;"><span style="white-space: pre;">    </span>// 下面重點構造 <span style="background-color: rgb(230, 230, 230); font-family: Arial;">struct logger_entry header 結構體</span></span><span style="color:#333333;">  
  15.     now = current_kernel_time();  
  16.   
  17.     header.pid = current->tgid;  
  18.     header.tid = current->pid;  
  19.     header.sec = now.tv_sec;  
  20.     header.nsec = now.tv_nsec;  
  21.     header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);  
  22.   
  23.     /* null writes succeed, return zero */  
  24.     if (unlikely(!header.len))  
  25.         return 0;  
  26.   
  27.     mutex_lock(&log->mutex);  
  28.   
  29.     /* 
  30.      * Fix up any readers, pulling them forward to the first readable 
  31.      * entry after (what will be) the new write offset. We do this now 
  32.      * because if we partially fail, we can end up with clobbered log 
  33.      * entries that encroach on readable buffer. 
  34.      */  
  35.     fix_up_readers(log, sizeof(struct logger_entry) + header.len);  
  36.   
  37.     do_write_log(log, &header, sizeof(struct logger_entry));  
  38.   
  39.     while (nr_segs-- > 0) {  
  40.         size_t len;  
  41.         ssize_t nr;  
  42.   
  43.         /* figure out how much of this vector we can keep */  
  44.         len = min_t(size_t, iov->iov_len, header.len - ret);  
  45.   
  46.         /* write out this segment's payload */  
  47.         nr = do_write_log_from_user(log, iov->iov_base, len);  
  48.         if (unlikely(nr < 0)) {  
  49.             log->w_off = orig;  
  50.             mutex_unlock(&log->mutex);  
  51.             return nr;  
  52.         }  
  53.   
  54.         iov++;  
  55.         ret += nr;  
  56.     }  
  57.   
  58.     mutex_unlock(&log->mutex);  
  59.   
  60.     /* wake up any blocked readers */  
  61.     wake_up_interruptible(&log->wq);  
  62.   
  63.     return ret;  
  64. }</span>  
首先利用內核信息構造header信息,然後寫入:do_write_log(log, &header, sizeof(struct logger_entry));

然後根據nr_segs數目,通過一個while循環把iov的內容寫入到日誌緩衝區中,也就是日誌的優先級別priority、日誌Tag和日誌主體Msg。


最後一個重要函數說明:

  1.     /* 
  2.      * Fix up any readers, pulling them forward to the first readable 
  3.      * entry after (what will be) the new write offset. We do this now 
  4.      * because if we partially fail, we can end up with clobbered log 
  5.      * entries that encroach on readable buffer. 
  6.      */  
  7.     fix_up_readers(log, sizeof(struct logger_entry) + header.len);  
  8.   
  9. /* 
  10.  * fix_up_readers - walk the list of all readers and "fix up" any who were 
  11.  * lapped by the writer; also do the same for the default "start head". 
  12.  * We do this by "pulling forward" the readers and start head to the first 
  13.  * entry after the new write head. 
  14.  * 
  15.  * The caller needs to hold log->mutex. 
  16.  */  
  17. static void fix_up_readers(struct logger_log *log, size_t len)  
  18. {  
  19.     size_t old = log->w_off;  
  20.     size_t new = logger_offset(old + len);  
  21.     struct logger_reader *reader;  
  22.   
  23.     if (clock_interval(old, new, log->head))  
  24.         log->head = get_next_entry(log, log->head, len);  
  25.   
  26.     list_for_each_entry(reader, &log->readers, list)  
  27.         if (clock_interval(old, new, reader->r_off))  
  28.             reader->r_off = get_next_entry(log, reader->r_off, len);  
  29. }  

爲何需要這麼一個函數呢?

由於日誌緩衝區是循環使用的,即舊的日誌記錄如果沒有及時讀取,而緩衝區的內容又已經用完時,就需要覆蓋舊的記錄來容納新的記錄。而這部分將要被覆蓋的內容,有可能是某些reader的下一次要讀取的日誌所在的位置,以及爲新的reader準備的日誌開始讀取位置head所在的位置。因此,需要調整這些位置,使它們能夠指向一個新的有效的位置。


4、日誌讀取重要過程分析:

註冊的讀取日誌設備文件的方法爲logger_read

  1. /* 
  2.  * logger_read - our log's read() method 
  3.  * 
  4.  * Behavior: 
  5.  * 
  6.  *  - O_NONBLOCK works 
  7.  *  - If there are no log entries to read, blocks until log is written to 
  8.  *  - Atomically reads exactly one log entry 
  9.  * 
  10.  * Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read 
  11.  * buffer is insufficient to hold next entry. 
  12.  */  
  13. static ssize_t logger_read(struct file *file, char __user *buf,  
  14.                size_t count, loff_t *pos)  
  15. {  
  16.     struct logger_reader *reader = file->private_data;  
  17.     struct logger_log *log = reader->log;  
  18.     ssize_t ret;  
  19.     DEFINE_WAIT(wait);  
  20.   
  21. start:  
  22.     while (1) {  
  23.         prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);  
  24.   
  25.         mutex_lock(&log->mutex);  
  26.         ret = (log->w_off == reader->r_off);  
  27.         mutex_unlock(&log->mutex);  
  28.         if (!ret)  
  29.             break;  
  30.   
  31.         if (file->f_flags & O_NONBLOCK) {  
  32.             ret = -EAGAIN;  
  33.             break;  
  34.         }  
  35.   
  36.         if (signal_pending(current)) {  
  37.             ret = -EINTR;  
  38.             break;  
  39.         }  
  40.   
  41.         schedule();  
  42.     }  
  43.   
  44.     finish_wait(&log->wq, &wait);  
  45.     if (ret)  
  46.         return ret;  
  47.   
  48.     mutex_lock(&log->mutex);  
  49.   
  50.     /* is there still something to read or did we race? */  
  51.     if (unlikely(log->w_off == reader->r_off)) {  
  52.         mutex_unlock(&log->mutex);  
  53.         goto start;  
  54.     }  
  55.   
  56.     /* get the size of the next entry */  
  57.     ret = get_entry_len(log, reader->r_off);  
  58.     if (count < ret) {  
  59.         ret = -EINVAL;  
  60.         goto out;  
  61.     }  
  62.   
  63.     /* get exactly one entry from the log */  
  64.     ret = do_read_log_to_user(log, reader, buf, ret);  
  65.   
  66. out:  
  67.     mutex_unlock(&log->mutex);  
  68.   
  69.     return ret;  
  70. }  

struct logger_reader *reader = file->private_data; 在這裏直接使用 file->private_data是因爲在device open時將private_data賦值爲reader,在文件操作方法 logger_open 中:

  1. <span style="color:#333333;">/* 
  2.  * logger_open - the log's open() file operation 
  3.  * 
  4.  * Note how near a no-op this is in the write-only case. Keep it that way! 
  5.  */  
  6. static int logger_open(struct inode *inode, struct file *file)  
  7. {  
  8.     struct logger_log *log;  
  9.     int ret;  
  10.   
  11.     ret = nonseekable_open(inode, file);  
  12.     if (ret)  
  13.         return ret;  
  14.   
  15.     log = get_log_from_minor(MINOR(inode->i_rdev));  
  16.     if (!log)  
  17.         return -ENODEV;  
  18.   
  19.     if (file->f_mode & FMODE_READ) {  
  20.         struct logger_reader *reader;  
  21.   
  22.         reader = kmalloc(sizeof(struct logger_reader), GFP_KERNEL);  
  23.         if (!reader)  
  24.             return -ENOMEM;  
  25.   
  26.         reader->log = log;  
  27.         INIT_LIST_HEAD(&reader->list);  
  28.   
  29.         mutex_lock(&log->mutex);  
  30.         reader->r_off = log->head; </span><span style="color:#ff0000;">// 從<span style="font-family: Arial; line-height: 26px;">log->head位置開始讀取日誌的,保存在struct logger_reader的成員變量r_off中</span></span><span style="color:#333333;">  
  31.         list_add_tail(&reader->list, &log->readers);  
  32.         mutex_unlock(&log->mutex);  
  33.   
  34.         </span><span style="color:#ff0000;">file->private_data = reader;  // 這裏對</span><span style="color: rgb(255, 0, 0); background-color: rgb(230, 230, 230); font-family: Arial;">private_data進行了賦值</span><span style="color:#ff0000;">  
  35. </span><span style="color:#333333;">  
  36.     } else  
  37.         file->private_data = log;  
  38.   
  39.     return 0;  
  40. }</span>  

首先在 while (1)循環中判定是否有日誌可讀,判定語句如下:

ret = (log->w_off == reader->r_off);

即判斷當前緩衝區的寫入位置和當前讀進程的讀取位置是否相等,如果不相等,則說明有新的日誌可讀。

首先通過get_entry_len獲取下一條可讀的日誌記錄的長度(日誌讀取進程是以日誌記錄爲單位進行讀取的,一次只讀取一條記錄

/* get the size of the next entry */
ret = get_entry_len(log, reader->r_off);

如果其中有數據時則利用do_read_log_to_user執行真正的讀取動作:

/* get exactly one entry from the log */
ret = do_read_log_to_user(log, reader, buf, ret);

下面我們仔細看下get_entry_len函數:

  1. /* 
  2.  * get_entry_len - Grabs the length of the payload of the next entry starting 
  3.  * from 'off'. 
  4.  * 
  5.  * Caller needs to hold log->mutex. 
  6.  */  
  7. static __u32 get_entry_len(struct logger_log *log, size_t off)  
  8. {  
  9.     __u16 val;  
  10.   
  11.     switch (log->size - off) {  
  12.     case 1:  
  13.         memcpy(&val, log->buffer + off, 1);  
  14.         memcpy(((char *) &val) + 1, log->buffer, 1);  
  15.         break;  
  16.     default:  
  17.         memcpy(&val, log->buffer + off, 2);  
  18.     }  
  19.   
  20.     return sizeof(struct logger_entry) + val;  
  21. }  
上面這段代碼第一次也看不很久,後來想到buffer是一個循環緩衝區終於明白啦!!

我們知道每一條日誌記錄由兩大部分組成,一部分是結構體:struct logger_entry,另外一部是payload有效負載即打印主體數據。
有效負載長度記錄在struct logger_entry中的len字段中,佔用兩個字節,與結構的struct logger_entry的首地址相同。因此只要讀取記錄
最前面兩個字節就可以了。
1、兩個字節連在一起,直接讀取即可,所以直接使用 memcpy(&val, log->buffer + off, 2); 
2、兩個字節不連在一起,則需要分別讀取,這種情況就是讀取緩衝區最後一個字節和第一個字節來獲取其長度,而此時r_off與size的長度相差1

ok,繼續分析真正的數據讀取函數:

  1. /* 
  2.  * do_read_log_to_user - reads exactly 'count' bytes from 'log' into the 
  3.  * user-space buffer 'buf'. Returns 'count' on success. 
  4.  * 
  5.  * Caller must hold log->mutex. 
  6.  */  
  7. static ssize_t do_read_log_to_user(struct logger_log *log,  
  8.                    struct logger_reader *reader,  
  9.                    char __user *buf,  
  10.                    size_t count)  
  11. {  
  12.     size_t len;  
  13.   
  14.     /* 
  15.      * We read from the log in two disjoint operations. First, we read from 
  16.      * the current read head offset up to 'count' bytes or to the end of 
  17.      * the log, whichever comes first. 
  18.      */  
  19.     len = min(count, log->size - reader->r_off);  
  20.     if (copy_to_user(buf, log->buffer + reader->r_off, len))  
  21.         return -EFAULT;  
  22.   
  23.     /* 
  24.      * Second, we read any remaining bytes, starting back at the head of 
  25.      * the log. 
  26.      */  
  27.     if (count != len)  
  28.         if (copy_to_user(buf + len, log->buffer, count - len))  
  29.             return -EFAULT;  
  30.   
  31.     reader->r_off = logger_offset(reader->r_off + count);  
  32.   
  33.     return count;  
  34. }  

根據緩衝區中數據分兩段的情況,調用copy_to_user函數來把位於內核空間的日誌緩衝區指定的內容拷貝到用戶空間的內存緩衝區就可以了,同時,把當前讀取日誌進程的上下文信息中的讀偏移r_off前進到下一條日誌記錄的開始的位置上。

5、其它函數:

logger_poll  用於log用戶態調用select函數進行查詢,利用

if (log->w_off != reader->r_off)
ret |= POLLIN | POLLRDNORM;

通知用戶是否有有日記需要讀取


logger_ioctl 用於一些常用的信息查詢,

#define LOGGER_GET_LOG_BUF_SIZE_IO(__LOGGERIO, 1) /* size of log */
#define LOGGER_GET_LOG_LEN _IO(__LOGGERIO, 2) /* used log len */
#define LOGGER_GET_NEXT_ENTRY_LEN _IO(__LOGGERIO, 3) /* next entry len */
#define LOGGER_FLUSH_LOG _IO(__LOGGERIO, 4) /* flush log */


獲取緩衝區中數據長度,下一個日誌的記錄,比較有意義的是 LOGGER_FLUSH_LOG:

list_for_each_entry(reader, &log->readers, list)
reader->r_off = log->w_off;
log->head = log->w_off;

清除緩衝區中的所有數據



發佈了73 篇原創文章 · 獲贊 20 · 訪問量 71萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章