EXT4檔案系統問題-多執行緒往TF卡寫MP4檔案後sync卡住問題分析-已解決
問題
多執行緒錄製攝像頭視訊檔案16MB,錄製完成後關閉檔案描述符,傳送sync進行系統呼叫,在多執行緒情況下,TF卡空閒容量小於1.5GB後會出現大概率的執行緒在sync卡住,導致執行緒進行D狀態。
執行緒卡住後的堆疊資訊:
[<c00756ec>] sleep_on_page+0x8/0x10
[<c007551c>] wait_on_page_bit+0xb4/0xbc
[<c0075648>] filemap_fdatawait_range+0xd4/0x130
[<c00756dc>] filemap_fdatawait+0x38/0x40
[<c00c0744>] sync_inodes_sb+0x108/0x13c
[<c00a3da8>] iterate_supers+0xa4/0xec
[<c00c43ac>] sys_sync+0x34/0x9c
[<c0012e40>] ret_fast_syscall+0x0/0x30
[<ffffffff>] 0xffffffff
核心
問題核心版本:linux-3.10.y(下面程式碼在linux-4.1.27中分析)
程式碼
Linux2.6.18核心版本後,改為了使用SYSCALL_DEFINEx來定義系統呼叫,但本質上還是sys_xxx的模式。後面的x表示引數個數,如sync系統呼叫:
SYSCALL_DEFINE0(sync) Sync everything
SYSCALL_DEFINE1(syncfs, int, fd) sync a single super
SYSCALL_DEFINE1(fsync, unsigned int, fd)
SYSCALL_DEFINE1(fdatasync, unsigned int, fd)
SYSCALL_DEFINE4(sync_file_range, int, fd, loff_t, offset, loff_t, nbytes,
unsigned int, flags)
sync系統呼叫函式實現:fs/sync.c
SYSCALL_DEFINE0(sync)
{
int nowait = 0, wait = 1;
wakeup_flusher_threads(0, WB_REASON_SYNC);
iterate_supers(sync_inodes_one_sb, NULL); 呼叫這裡
iterate_supers(sync_fs_one_sb, &nowait);
iterate_supers(sync_fs_one_sb, &wait);
iterate_bdevs(fdatawrite_one_bdev, NULL);
iterate_bdevs(fdatawait_one_bdev, NULL);
if (unlikely(laptop_mode))
laptop_sync_completion();
return 0;
}
根據堆疊資訊,繼續分析函式呼叫:
static void sync_inodes_one_sb(struct super_block *sb, void *arg)
{
if (!(sb->s_flags & MS_RDONLY)) 檢查檔案系統是否掛載為只讀,只讀就不用sync了
sync_inodes_sb(sb);
}
傳入引數是super_block,程式碼了VFS層的檔案系統,可以操作到下面所有的具體檔案系統:
/**
* sync_inodes_sb - sync sb inode pages
* @sb: the superblock
*
* This function writes and waits on any dirty inode belonging to this
* super_block.
*/
void sync_inodes_sb(struct super_block *sb)
{
DECLARE_COMPLETION_ONSTACK(done);
struct wb_writeback_work work = {
.sb = sb,
.sync_mode = WB_SYNC_ALL,
.nr_pages = LONG_MAX,
.range_cyclic = 0,
.done = &done,
.reason = WB_REASON_SYNC,
.for_sync = 1,
};
/* Nothing to do? */
if (sb->s_bdi == &noop_backing_dev_info)
return;
WARN_ON(!rwsem_is_locked(&sb->s_umount));
bdi_queue_work(sb->s_bdi, &work);
wait_for_completion(&done);
wait_sb_inodes(sb); 等待cache中所有的髒inode全部寫入,阻塞
}
wait_sb_inodes中在inode hash表中查詢所有的髒資料,inode代表檔案資訊資料,並通過filemap_fdatawait(mapping);把inode對應的資料寫入磁碟且阻塞:
static void wait_sb_inodes(struct super_block *sb)
{
struct inode *inode, *old_inode = NULL;
/*
* We need to be protected against the filesystem going from
* r/o to r/w or vice versa.
*/
WARN_ON(!rwsem_is_locked(&sb->s_umount));
spin_lock(&inode_sb_list_lock);
list_for_each_entry(inode, &sb->s_inodes, i_sb_list) { 查詢
struct address_space *mapping = inode->i_mapping; 資料對映賦值
spin_lock(&inode->i_lock);
if ((inode->i_state & (I_FREEING|I_WILL_FREE|I_NEW)) ||
(mapping->nrpages == 0)) {
spin_unlock(&inode->i_lock);
continue;
}
__iget(inode);
spin_unlock(&inode->i_lock);
spin_unlock(&inode_sb_list_lock);
iput(old_inode);
old_inode = inode;
filemap_fdatawait(mapping); 寫資料到磁碟
cond_resched();
spin_lock(&inode_sb_list_lock);
}
spin_unlock(&inode_sb_list_lock);
iput(old_inode);
}
繼續分析,獲取檔案資料的大小i_size:
int filemap_fdatawait(struct address_space *mapping)
{
loff_t i_size = i_size_read(mapping->host);
if (i_size == 0)
return 0;
return filemap_fdatawait_range(mapping, 0, i_size - 1);
}
讀取檔案範圍內佔用多少page資料,並把每個page資料阻塞的刷如磁碟。
int filemap_fdatawait_range(struct address_space *mapping, loff_t start_byte,
loff_t end_byte)
{
pgoff_t index = start_byte >> PAGE_CACHE_SHIFT;
pgoff_t end = end_byte >> PAGE_CACHE_SHIFT;
struct pagevec pvec;
int nr_pages;
int ret2, ret = 0;
if (end_byte < start_byte)
goto out;
pagevec_init(&pvec, 0);
while ((index <= end) &&
(nr_pages = pagevec_lookup_tag(&pvec, mapping, &index,
PAGECACHE_TAG_WRITEBACK,
min(end - index, (pgoff_t)PAGEVEC_SIZE-1) + 1)) != 0) {
unsigned i;
for (i = 0; i < nr_pages; i++) {
struct page *page = pvec.pages[i];
/* until radix tree lookup accepts end_index */
if (page->index > end)
continue;
wait_on_page_writeback(page);
if (TestClearPageError(page))
ret = -EIO;
}
pagevec_release(&pvec);
cond_resched();
}
out:
ret2 = filemap_check_errors(mapping);
if (!ret)
ret = ret2;
return ret;
}
/*
* Wait for a page to complete writeback
*/
static inline void wait_on_page_writeback(struct page *page)
{
if (PageWriteback(page))
wait_on_page_bit(page, PG_writeback);
}
void wait_on_page_bit(struct page *page, int bit_nr)
{
DEFINE_WAIT_BIT(wait, &page->flags, bit_nr);
if (test_bit(bit_nr, &page->flags))
__wait_on_bit(page_waitqueue(page), &wait, bit_wait_io,
TASK_UNINTERRUPTIBLE);
}
int __sched
__wait_on_bit(wait_queue_head_t *wq, struct wait_bit_queue *q,
wait_bit_action_f *action, unsigned mode)
{
int ret = 0;
do {
prepare_to_wait(wq, &q->wait, mode);
if (test_bit(q->key.bit_nr, q->key.flags))
ret = (*action)(&q->key);
} while (test_bit(q->key.bit_nr, q->key.flags) && !ret);
finish_wait(wq, &q->wait);
return ret;
}
3.10核心的action是sleep_on_page,4.1核心是bit_wait_io,
static int sleep_on_page(void *word)
{
io_schedule();
return 0;
}
__sched int bit_wait_io(struct wait_bit_key *word)
{
if (signal_pending_state(current->state, current))
return 1;
io_schedule();
return 0;
}
核心3.10不帶超時,那如果卡住會怎樣呢?死等嗎?【】
請求CPU寫page記憶體資料到磁碟,在加入請求佇列後什麼情況下無限期的卡住?【】
void __sched io_schedule(void)
{
struct rq *rq = raw_rq();
delayacct_blkio_start();
atomic_inc(&rq->nr_iowait);
blk_flush_plug(current);
current->in_iowait = 1;
schedule();
current->in_iowait = 0;
atomic_dec(&rq->nr_iowait);
delayacct_blkio_end();
}
有帶超時函式卻沒有使用
long __sched io_schedule_timeout(long timeout)
{
struct rq *rq = raw_rq();
long ret;
delayacct_blkio_start();
atomic_inc(&rq->nr_iowait);
blk_flush_plug(current);
current->in_iowait = 1;
ret = schedule_timeout(timeout);
current->in_iowait = 0;
atomic_dec(&rq->nr_iowait);
delayacct_blkio_end();
return ret;
}
核心4.1是帶超時的返回的,
static inline void io_schedule(void)
{
io_schedule_timeout(MAX_SCHEDULE_TIMEOUT);
}
long __sched io_schedule_timeout(long timeout)
{
int old_iowait = current->in_iowait;
struct rq *rq;
long ret;
current->in_iowait = 1;
blk_schedule_flush_plug(current);
delayacct_blkio_start();
rq = raw_rq();
atomic_inc(&rq->nr_iowait);
ret = schedule_timeout(timeout);
current->in_iowait = old_iowait;
atomic_dec(&rq->nr_iowait);
delayacct_blkio_end();
return ret;
}
測試
1、加入io_schedule_timeout機制是否還會卡住?【也會卡住】
修改核心3.10程式碼:
./mm/filemap.c +179
#include <linux/jiffies.h>
static int sleep_on_page(void *word)
{
- io_schedule();
+ io_schedule_timeout(msecs_to_jiffies(20000));//20s
return 0;
}
編譯成功,進行測試【出現問題時間變久,但最後還是會出現;理論不可行,最多能規避但沒有找到原因不能實質性解決問題】
2、使用fsync(fd)而不用sync【也會卡住】
晚上測試明早看結果【也會卡住】
3、檢視sync後進入不可終端的睡眠狀態為何沒有條件來喚醒【】
4、修改I/O排程器模式:
預設是deadline,修改為cfq【測試2小時刪除88次後sync卡住】
/sys/block/sdb/queue/scheduler
修改為noop模式進行測試【】
進展
Linux 3.10.y多執行緒寫SD卡後sync會卡住,目前發現drivers/scsi/sd.c檔案中sd_prep_fn函式在組裝scsi cmd時,寫的物理sector位置觸及到SD卡邊界,程式碼如下:
/*
* Some SD card readers can't handle multi-sector accesses which touch
* the last one or two hardware sectors. Split accesses as needed.
*/
threshold = get_capacity(disk) - SD_LAST_BUGGY_SECTORS *
(sdp->sector_size / 512);
if (unlikely(sdp->last_sector_bug && block + this_count > threshold)) {
printk(KERN_ERR "eason %s,%d,--%d,%llu,%llu\n",__FUNCTION__, __LINE__,this_count,(unsigned long long)block,(unsigned long long)threshold);
if (block < threshold) {
/* Access up to the threshold but not beyond */
this_count = threshold - block;
} else {
/* Access only a single hardware sector */
this_count = sdp->sector_size / 512;
printk(KERN_ERR "this_count=%d\n",this_count);
}
}
物理位置最後的8個sector(4KB)會拆分成每個sector下發到scsi cmd,但不知為何會導致執行緒下刷page卡住,沒有寫進磁碟裝置還是寫成功後中斷沒有合併返回,具體原因待查【卡住時8個單獨的sector是寫成功的】。不知為何,格式化時寫到邊界的8個sector是可以返回的,多程式同時寫測試邊界的8個sector就不返回了嗎?
bio=14102864 sectos,1762858 page 7220666368 bytes
in=0
1=1762858
2=1762858
3=0
4=1762858
5=0
6=0
7=0
run=2925699072 byte,714282 page,2925699072 + 4294967296(2^32) = 7220666368
irq=60946
out=60946
eason sd_prep_fn,953,--8,15523832,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--7,15523833,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--6,15523834,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--5,15523835,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--4,15523836,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--3,15523837,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--2,15523838,15523824
eason scsi_io_completion,836,good_bytes=512
eason sd_prep_fn,953,--1,15523839,15523824
eason scsi_io_completion,836,good_bytes=512
檢視問題SD卡的sector容量和地址範圍如下:
Disk /dev/sdb: 7948 MB, 7948206080 bytes
255 heads, 63 sectors/track, 966 cylinders, total 15523840 sectors == 7948206080 bytes
Units = sectors of 1 * 512 = 512 bytes
Device Boot Start End Blocks Id System
/dev/sdb1 8192 15523839 7757824 c Win95 FAT32 (LBA)
Partition 1 has different physical/logical endings:
phys=(965, 254, 63) logical=(966, 80, 10)
end 15523839剛好到容量範圍15523840的邊界,所以會在格式化和卡寫完時大概率寫到邊界觸發卡住問題。重新在板子上面進行分割槽:
Disk /dev/sdb: 7948 MB, 7948206080 bytes
255 heads, 63 sectors/track, 966 cylinders, total 15523840 sectors
Units = sectors of 1 * 512 = 512 bytes
Device Boot Start End Blocks Id System
/dev/sdb1 63 15518789 7759363+ 83 Linux
可以看到end的位置已經變化,距離容量邊界相差5051個sector,所以任何情況下不會寫到邊界,在後面測試過程中沒有出現問題。我們的SD卡只有1個分割槽,且是卡廠家出廠的預設值,我們只是把預設的fat32重新格式化了ext4,分割槽沒有動過。
拿到電腦上進行分割槽和格式化測試如下:
在Linux上格式化後資訊:
刪除分割槽重新新建分割槽:全部按照預設值操作,應該可以設定保留
建立新分割槽時的預設值:可以看到使用了全部的柱面。
Disk /dev/sdb: 7948 MB, 7948206080 bytes
255 heads, 63 sectors/track, 966 cylinders, total 15523840 sectors
Units = sectors of 1 * 512 = 512 bytes
Device Boot Start End Blocks Id System
/dev/sdb1 * 2048 15523839 7760896 b Win95 FAT32
Partition 1 has different physical/logical endings:
phys=(965, 254, 63) logical=(966, 80, 10)
SD卡出廠預設資訊:
可總結出:分割槽總扇區數 + 起始扇區號 = 裝置總扇區數,但是在Linux上面分割槽時末尾有預留,就不會到訪問到邊界。問題情況瞭解了,繼續查問題原因:
為啥訪問到SD卡物理邊界就會導致寫檔案卡死??
原因
不知為何,格式化時寫到邊界的8個sector是可以返回的,多程式同時寫測試邊界的8個sector就不返回了嗎?
高版本核心3.18.108沒有這個問題,為啥?已經解決此問題了嗎
具體原因見:Linux ext4檔案系統多執行緒寫檔案sync卡住分析
相關文章
- 檔案系統變成RAW問題解決
- rsync同步檔案到遠端機器,卡住10多秒--問題解決過程
- 多執行緒問題解釋執行緒
- 徹底解決Hive小檔案問題Hive
- Linux EXT4檔案系統TF卡空間容量顯示和計算Linux
- 多執行緒的安全問題及解決方案執行緒
- [問題]多個檔案寫入日誌報錯
- Java IO 建立檔案解決檔名重複問題Java
- HashMap多執行緒併發問題分析HashMap執行緒
- 多執行緒引起的效能問題分析執行緒
- 解決 sublime text3 執行python檔案無法input的問題Python
- 多執行緒下載檔案執行緒
- 多執行緒併發同步問題及解決方案執行緒
- Java中解決多執行緒資料安全問題Java執行緒
- 從cmake解決clion編譯生成的可執行檔案(.exe)不可執行的問題編譯
- 解決SqlServer執行指令碼,檔案過大,記憶體溢位問題SQLServer指令碼記憶體溢位
- SpringBoot 解決打包釋出後讀取不到 json 檔案問題Spring BootJSON
- 解決Git已經提交了專案再新增.gitignore檔案不生效的問題Git
- 解決Reiserfs檔案系統損壞的問題我們是認真的
- dotnet 已知問題 警惕 StreamReader 的 EndOfStream 卡住執行緒執行緒
- 如何解決多執行緒併發問題執行緒
- 解決excel開啟.csv檔案亂碼問題Excel
- Linux中常見的檔案讀寫錯誤問題及解決方法!Linux
- 多執行緒相關問題執行緒
- Python | 多執行緒死鎖問題的巧妙解決方法Python執行緒
- 【Linux】解決tomcat出現Toomanyopenfiles(開啟的檔案過多)問題LinuxTomcatOOM
- 多執行緒併發安全問題詳解執行緒
- 簡單瞭解EXT4檔案系統
- ocfs2檔案系統問題總結(zt)
- Java HDFS API 追加檔案寫入內容異常問題的解決JavaAPI
- 解決PHP匯出CSV檔案中文亂碼問題PHP
- DataGuard ORA-01111檔案建立失敗問題解決
- 解決properties、xml等配置檔案無法找到問題XML
- Python執行緒安全問題及解決方法Python執行緒
- java多執行緒程式設計問題以及解決辦法Java執行緒程式設計
- 記一個 FormData 多檔案上傳問題ORM
- 多執行緒之8鎖問題執行緒
- 05.java多執行緒問題Java執行緒