主頁 >  其他 > Linux內核檔案系統11

Linux內核檔案系統11

2021-12-20 16:24:57 其他

2021SC@SDUSC

inode.c(2)

/*
* 請注意,除非我們記錄資料,否則我們不需要啟動事務,因為我們應該從 ext4_page_mkwrite() 填充漏洞,
  我們甚至不需要在有序模式下將 inode 歸檔到事務串列中,
  因為如果我們寫回通過 write() 添加的資料,inode 已經在那里,
  如果我們寫回通過 mmap() 修改的資料,沒有人保證資料將在哪個事務中訪問磁盤,
  如果我們正在記錄資料,我們不能直接啟動事務,因為事務啟動高于頁面鎖,所以我們必須做一些magic,
 * 這個函式可以通過...
   - 獲取頁面鎖定后的 ext4_writepages(有日志句柄)
   - journal_submit_inode_data_buffers(無日志句柄)
   - 通過 kswapd/直接回收(無日志句柄)的shrink_page_list
   - 執行write_begin 時grab_page_cache(有日志句柄)
 * 我們在這個函式中不做任何塊分配,如果我們有多個塊的頁面,我們需要寫入那些被映射的 buffer_heads,
   這對于基于 mmaped 的寫入很重要,因此,如果我們使用塊大小 1K truncate(f, 1024); a = mmap(f, 0, 4096); a[0] = 'a';截斷(f,4096);
   我們在頁面中通過 page_mkwrite 回呼映射了第一個 buffer_head,但其他 buffer_heads 將未映射但很臟(通過 do_wp_page 完成),
   所以 writepage 應該寫第一個塊,如果我們修改 mmap 區域超過 1024,我們將再次得到 page_fault 并且 page_mkwrite 回呼將進行塊分配并標記已映射的 buffer_heads,
 * 如果頁面中有任何延遲或未寫入的 buffer_heads,我們會重臟頁面,
 * 我們可以遞回呼叫如下所示,
 * ext4_writepage() -> kmalloc() -> __alloc_pages() -> page_launder() -> ext4_writepage()
 * 但是由于我們不做任何塊分配,所以我們不應該死鎖,頁面也清除了臟標志,因此我們不會獲得遞回 page_lock,
 */
static int ext4_writepage(struct page *page,
			  struct writeback_control *wbc)
{
	int ret = 0;
	loff_t size;
	unsigned int len;
	struct buffer_head *page_bufs = NULL;
	struct inode *inode = page->mapping->host;
	struct ext4_io_submit io_submit;
	bool keep_towrite = false;

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb)))) {
		inode->i_mapping->a_ops->invalidatepage(page, 0, PAGE_SIZE);
		unlock_page(page);
		return -EIO;
	}

	trace_ext4_writepage(page);
	size = i_size_read(inode);
	if (page->index == size >> PAGE_SHIFT &&
	    !ext4_verity_in_progress(inode))
		len = size & ~PAGE_MASK;
	else
		len = PAGE_SIZE;

	page_bufs = page_buffers(page);
	/*
	 * 我們無法在此函式中進行塊分配或其他范圍處理, 如果有緩沖區需要,我們必須重新臟頁面, 
	  但是當我們通過 journal_submit_inode_data_buffers() 進行日志提交時,我們可能會到達這里,
	  在這種情況下,我們必須寫入分配的緩沖區以實作 data=ordered 模式保證,
	 * 另外,如果每頁只有一個緩沖區(fs塊大小==頁大小),如果一個緩沖區需要塊分配或需要修改extent樹清除未寫標志,
	   我們知道該頁不能 完全被寫入,所以我們不妨立即拒絕寫入, 
	   不幸的是,如果塊大小 != 頁面大小,我們不能使用 ext4_walk_page_buffers() 輕松檢測這種情況,
	   但對于極其常見的情況,這是一種優化,可以跳過 ext4_bio_write_page() 的無用往返,
	 */
	if (ext4_walk_page_buffers(NULL, page_bufs, 0, len, NULL,
				   ext4_bh_delay_or_unwritten)) {
		redirty_page_for_writepage(wbc, page);
		if ((current->flags & PF_MEMALLOC) ||
		    (inode->i_sb->s_blocksize == PAGE_SIZE)) {
			/*
			 * 對于記憶體清理,只寫一些緩沖區是沒有意義的, 所以只能保釋, 如果我們從直接回收來到這里,請警告,
			 */
			WARN_ON_ONCE((current->flags & (PF_MEMALLOC|PF_KSWAPD))
							== PF_MEMALLOC);
			unlock_page(page);
			return 0;
		}
		keep_towrite = true;
	}

	if (PageChecked(page) && ext4_should_journal_data(inode))
		/*
		 * 它是映射的頁面快取, 添加緩沖區并記錄它, 在這里重新修改頁面似乎沒有多大意義,
		 */
		return __ext4_journalled_writepage(page, len);

	ext4_io_submit_init(&io_submit, wbc);
	io_submit.io_end = ext4_init_io_end(inode, GFP_NOFS);
	if (!io_submit.io_end) {
		redirty_page_for_writepage(wbc, page);
		unlock_page(page);
		return -ENOMEM;
	}
	ret = ext4_bio_write_page(&io_submit, page, len, keep_towrite);
	ext4_io_submit(&io_submit);
	/* 洗掉我們從 init 得到的 io_end 參考 */
	ext4_put_io_end_defer(io_submit.io_end);
	return ret;
}

static int mpage_submit_page(struct mpage_da_data *mpd, struct page *page)
{
	int len;
	loff_t size;
	int err;

	BUG_ON(page->index != mpd->first_page);
	clear_page_dirty_for_io(page);
	/*
	 *我們在這里必須非常小心! 沒有什么可以保護寫回路徑免受 i_size 更改的影響,
	  并且頁面可以可寫地映射到頁表中,
	  因此,應用程式可以在寫回運行時增加 i_size 并通過 mmap 寫入資料, 
	  clear_page_dirty_for_io() 在頁表中對我們的頁進行寫保護,
	  并且在我們釋放頁鎖之前無法再次寫入該頁, 
	  因此,只有在 clear_page_dirty_for_io() 之后,
	  我們才能安全地對 ext4_bio_write_page() 的 i_size 進行采樣,以將寫入頁面的尾部清零, 
	  我們依靠在 clear_page_dirty_for_io() 中 TestClearPageDirty 提供的屏障來確保 i_size 僅在頁表更新后才真正被采樣,
	 */
	size = i_size_read(mpd->inode);
	if (page->index == size >> PAGE_SHIFT &&
	    !ext4_verity_in_progress(mpd->inode))
		len = size & ~PAGE_MASK;
	else
		len = PAGE_SIZE;
	err = ext4_bio_write_page(&mpd->io_submit, page, len, false);
	if (!err)
		mpd->wbc->nr_to_write--;
	mpd->first_page++;

	return err;
}

#define BH_FLAGS (BIT(BH_Unwritten) | BIT(BH_Delay))

/*
 * mballoc 最多給我們這個數量的塊...
 * XXX:這似乎只是 ext4_mb_normalize_request() 的限制,
 * mballoc 的其余部分似乎可以處理最大組大小的塊,
 */
#define MAX_WRITEPAGES_EXTENT_LEN 2048

/*
 * mpage_add_bh_to_extent - 嘗試將 bh 添加到要映射的塊范圍
 * @mpd - 塊的范圍
 * @lblk - 檔案中塊的邏輯編號
 * @bh - 我們要添加到范圍的緩沖區頭
 * 該函式用于收集contig, 塊處于相同狀態, 
   如果緩沖區不需要回寫映射,并且我們還沒有開始映射緩沖區的范圍,則該函式立即回傳true——呼叫者可以立即寫入緩沖區, 
   否則,如果塊已添加到范圍,則函式回傳 true,如果無法添加塊,則回傳 false,
 */
static bool mpage_add_bh_to_extent(struct mpage_da_data *mpd, ext4_lblk_t lblk,
				   struct buffer_head *bh)
{
	struct ext4_map_blocks *map = &mpd->map;

	/* 寫回不需要映射的緩沖區 */
	if (!buffer_dirty(bh) || !buffer_mapped(bh) ||
	    (!buffer_delay(bh) && !buffer_unwritten(bh))) {
		/* 到目前為止還沒有映射的范圍 => 我們立即寫入緩沖區 */
		if (map->m_len == 0)
			return true;
		return false;
	}

	/* 范圍內的第一個塊? */
	if (map->m_len == 0) {
		/* 除非句柄啟動,否則我們無法映射... */
		if (!mpd->do_map)
			return false;
		map->m_lblk = lblk;
		map->m_len = 1;
		map->m_flags = bh->b_state & BH_FLAGS;
		return true;
	}

	/* 不要超過 mballoc 愿意分配的大小 */
	if (map->m_len >= MAX_WRITEPAGES_EXTENT_LEN)
		return false;

	/* 我們可以將塊合并到我們的大范圍嗎? */
	if (lblk == map->m_lblk + map->m_len &&
	    (bh->b_state & BH_FLAGS) == map->m_flags) {
		map->m_len++;
		return true;
	}
	return false;
}

/*
 * mpage_process_page_bufs - 為 IO 提交頁面緩沖區或將它們添加到范圍
  *
  * @mpd - 映射塊的范圍
  * @head - 頁面中的第一個緩沖區
  * @bh - 我們應該從緩沖區開始處理
  * @lblk - 檔案中與@bh 對應的塊的邏輯編號
  *
  * 遍歷從@bh 到@head(獨占)的頁面緩沖區,如果此頁面中的所有緩沖區都已映射并且沒有累積的緩沖區范圍要映射,
    或者將頁面中的緩沖區添加到緩沖區的范圍,則提交頁面以進行 IO 地圖, 
	如果呼叫者可以繼續處理下一頁,則該函式回傳 1,如果它應該停止向要映射的范圍添加緩沖區,則回傳 0,因為我們無法再擴展它, 
	它也可以在 IO 提交程序中出錯時回傳值 < 0,
 */
static int mpage_process_page_bufs(struct mpage_da_data *mpd,
				   struct buffer_head *head,
				   struct buffer_head *bh,
				   ext4_lblk_t lblk)
{
	struct inode *inode = mpd->inode;
	int err;
	ext4_lblk_t blocks = (i_size_read(inode) + i_blocksize(inode) - 1)
							>> inode->i_blkbits;

	if (ext4_verity_in_progress(inode))
		blocks = EXT_MAX_BLOCKS;

	do {
		BUG_ON(buffer_locked(bh));

		if (lblk >= blocks || !mpage_add_bh_to_extent(mpd, lblk, bh)) {
			/* 找到要映射的范圍 */
			if (mpd->map.m_len)
				return 0;
			/* 緩沖區需要映射,句柄未啟動? */
			if (!mpd->do_map)
				return 0;
			/* 到目前為止一切都已映射,我們達到了 EOF */
			break;
		}
	} while (lblk++, (bh = bh->b_this_page) != head);
	/* 到目前為止一切都被映射了嗎? 提交 IO 頁面, */
	if (mpd->map.m_len == 0) {
		err = mpage_submit_page(mpd, head->b_page);
		if (err < 0)
			return err;
	}
	if (lblk >= blocks) {
		mpd->scanned_until_end = 1;
		return 0;
	}
	return 1;
}

/*
  * mpage_process_page - 更新對應于改變范圍的頁面緩沖區,并且可以為 IO 提交完全映射的頁面
  *
  * @mpd - 要映射的范圍的描述,回傳下一個要映射的范圍
  * @m_lblk - 邏輯塊映射,
  * @m_pblk - 對應的物理映射,
  * @map_bh - 在回傳時確定此頁面是否需要進一步映射,
  * 掃描與改變的范圍對應的給定頁面緩沖區,并根據新的范圍狀態更新緩沖區狀態,
  * 我們將 delalloc 緩沖區映射到它們的物理位置,清除未寫入的位, 
    如果給定頁面沒有完全映射,我們將@map 更新到給定頁面中需要映射的下一個范圍并將@map_bh 回傳為true,
 */
static int mpage_process_page(struct mpage_da_data *mpd, struct page *page,
			      ext4_lblk_t *m_lblk, ext4_fsblk_t *m_pblk,
			      bool *map_bh)
{
	struct buffer_head *head, *bh;
	ext4_io_end_t *io_end = mpd->io_submit.io_end;
	ext4_lblk_t lblk = *m_lblk;
	ext4_fsblk_t pblock = *m_pblk;
	int err = 0;
	int blkbits = mpd->inode->i_blkbits;
	ssize_t io_end_size = 0;
	struct ext4_io_end_vec *io_end_vec = ext4_last_io_end_vec(io_end);

	bh = head = page_buffers(page);
	do {
		if (lblk < mpd->map.m_lblk)
			continue;
		if (lblk >= mpd->map.m_lblk + mpd->map.m_len) {
			/*
			 * 映射范圍結束后的緩沖區,
			 * 查找頁面中的下一個緩沖區以進行映射,
			 */
			mpd->map.m_len = 0;
			mpd->map.m_flags = 0;
			io_end_vec->size += io_end_size;
			io_end_size = 0;

			err = mpage_process_page_bufs(mpd, head, bh, lblk);
			if (err > 0)
				err = 0;
			if (!err && mpd->map.m_len && mpd->map.m_lblk > lblk) {
				io_end_vec = ext4_alloc_io_end_vec(io_end);
				if (IS_ERR(io_end_vec)) {
					err = PTR_ERR(io_end_vec);
					goto out;
				}
				io_end_vec->offset = (loff_t)mpd->map.m_lblk << blkbits;
			}
			*map_bh = true;
			goto out;
		}
		if (buffer_delay(bh)) {
			clear_buffer_delay(bh);
			bh->b_blocknr = pblock++;
		}
		clear_buffer_unwritten(bh);
		io_end_size += (1 << blkbits);
	} while (lblk++, (bh = bh->b_this_page) != head);

	io_end_vec->size += io_end_size;
	io_end_size = 0;
	*map_bh = false;
out:
	*m_lblk = lblk;
	*m_pblk = pblock;
	return err;
}

/*
  * mpage_map_buffers - 更新對應于改變范圍的緩沖區并提交完全映射的頁面用于 IO
  *
  * @mpd - 要映射的范圍的描述,回傳下一個要映射的范圍
  *
  * 掃描改變范圍對應的緩沖區(我們期望相應的頁面已經被鎖定)并根據新的范圍狀態更新緩沖區狀態,
  * 我們將 delalloc 緩沖區映射到它們的物理位置,清除未寫入的位,并在我們對未寫入的盤區執行寫入時將緩沖區標記為 uninit,并在 IO 完成后進行盤區轉換, 
	如果最后一頁沒有完全映射,我們將@map 更新到最后一頁中需要映射的下一個范圍, 否則我們提交頁面以進行 IO,
 */
static int mpage_map_and_submit_buffers(struct mpage_da_data *mpd)
{
	struct pagevec pvec;
	int nr_pages, i;
	struct inode *inode = mpd->inode;
	int bpp_bits = PAGE_SHIFT - inode->i_blkbits;
	pgoff_t start, end;
	ext4_lblk_t lblk;
	ext4_fsblk_t pblock;
	int err;
	bool map_bh = false;

	start = mpd->map.m_lblk >> bpp_bits;
	end = (mpd->map.m_lblk + mpd->map.m_len - 1) >> bpp_bits;
	lblk = start << bpp_bits;
	pblock = mpd->map.m_pblk;

	pagevec_init(&pvec);
	while (start <= end) {
		nr_pages = pagevec_lookup_range(&pvec, inode->i_mapping,
						&start, end);
		if (nr_pages == 0)
			break;
		for (i = 0; i < nr_pages; i++) {
			struct page *page = pvec.pages[i];

			err = mpage_process_page(mpd, page, &lblk, &pblock,
						 &map_bh);
			/*
			 * 如果 map_bh 為 true,則意味著頁面可能需要進一步的 bh 映射,或者頁面可能已提交用于 IO, 所以我們回傳呼叫進一步的范圍映射,
			 */
			if (err < 0 || map_bh)
				goto out;
			/* 頁面完全映射 - 讓 IO 運行 */
			err = mpage_submit_page(mpd, page);
			if (err < 0)
				goto out;
		}
		pagevec_release(&pvec);
	}
	/* 范圍完全映射并與頁面邊界匹配 */
	mpd->map.m_len = 0;
	mpd->map.m_flags = 0;
	return 0;
out:
	pagevec_release(&pvec);
	return err;
}

static int mpage_map_one_extent(handle_t *handle, struct mpage_da_data *mpd)
{
	struct inode *inode = mpd->inode;
	struct ext4_map_blocks *map = &mpd->map;
	int get_blocks_flags;
	int err, dioread_nolock;

	trace_ext4_da_write_pages_extent(inode, map);
	/*
	 * 呼叫 ext4_map_blocks() 來分配任何延遲的分配塊,或將未寫入的范圍轉換為初始化(在我們已寫入一個或多個預分配塊的情況下), 
	   我們可能需要比以前保留的更多的元資料塊, 但是我們不能失敗,因為我們處于寫回狀態,我們對此無能為力,因此可能會導致資料丟失, 
	   因此,如果可能,請使用保留塊來分配元資料,
	 *
	 * 如果有問題的塊是 delalloc 塊,我們將傳入魔法 EXT4_GET_BLOCKS_DELALLOC_RESERVE, 這表明在將資料復制到頁面快取中時已經檢查了塊和配額,
	 */
	get_blocks_flags = EXT4_GET_BLOCKS_CREATE |
			   EXT4_GET_BLOCKS_METADATA_NOFAIL |
			   EXT4_GET_BLOCKS_IO_SUBMIT;
	dioread_nolock = ext4_should_dioread_nolock(inode);
	if (dioread_nolock)
		get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
	if (map->m_flags & BIT(BH_Delay))
		get_blocks_flags |= EXT4_GET_BLOCKS_DELALLOC_RESERVE;

	err = ext4_map_blocks(handle, inode, map, get_blocks_flags);
	if (err < 0)
		return err;
	if (dioread_nolock && (map->m_flags & EXT4_MAP_UNWRITTEN)) {
		if (!mpd->io_submit.io_end->handle &&
		    ext4_handle_valid(handle)) {
			mpd->io_submit.io_end->handle = handle->h_rsv_handle;
			handle->h_rsv_handle = NULL;
		}
		ext4_set_io_unwritten_flag(inode, mpd->io_submit.io_end);
	}

	BUG_ON(map->m_len == 0);
	return 0;
}

/*
* mpage_map_and_submit_extent - 映射范圍從 mpd->lblk 開始,長度為 mpd->len 并提交它下面的頁面用于 IO
 *
 * @handle - 日志操作的句柄
 * @mpd - 映射范圍
 * @give_up_on_write - 如果存在致命錯誤并且沒有希望寫入資料,我們將其設定為 true,呼叫者應該丟棄臟頁以避免無限回圈,
 *
 * 該函式映射范圍從 mpd->lblk 開始,長度為 mpd->len,如果延遲,則分配塊,如果未寫入,我們可能需要將它們轉換為已初始化或從更大的未寫入范圍拆分描述的范圍,
   請注意,我們不需要映射所有描述的范圍,因為分配可以回傳更少的塊或者該范圍被更多未寫入的范圍覆寫,
   我們無法映射更多,因為我們受到保留交易信用的限制,另一方面,我們始終確保最后觸摸的頁面已完全映射,以便可以將其寫出(從而保證前進的進度),
   映射后,我們提交所有映射頁面進行 IO,
 */
static int mpage_map_and_submit_extent(handle_t *handle,
				       struct mpage_da_data *mpd,
				       bool *give_up_on_write)
{
	struct inode *inode = mpd->inode;
	struct ext4_map_blocks *map = &mpd->map;
	int err;
	loff_t disksize;
	int progress = 0;
	ext4_io_end_t *io_end = mpd->io_submit.io_end;
	struct ext4_io_end_vec *io_end_vec;

	io_end_vec = ext4_alloc_io_end_vec(io_end);
	if (IS_ERR(io_end_vec))
		return PTR_ERR(io_end_vec);
	io_end_vec->offset = ((loff_t)map->m_lblk) << inode->i_blkbits;
	do {
		err = mpage_map_one_extent(handle, mpd);
		if (err < 0) {
			struct super_block *sb = inode->i_sb;

			if (ext4_forced_shutdown(EXT4_SB(sb)) ||
			    ext4_test_mount_flag(sb, EXT4_MF_FS_ABORTED))
				goto invalidate_dirty_pages;
			/*
			 * 讓上層重試瞬時錯誤,
			 * 在 ENOSPC 的情況下,如果 ext4_count_free_blocks() 非零,提交應該釋放塊,
			 */
			if ((err == -ENOMEM) ||
			    (err == -ENOSPC && ext4_count_free_clusters(sb))) {
				if (progress)
					goto update_disksize;
				return err;
			}
			ext4_msg(sb, KERN_CRIT,
				 "Delayed block allocation failed for "
				 "inode %lu at logical offset %llu with"
				 " max blocks %u with error %d",
				 inode->i_ino,
				 (unsigned long long)map->m_lblk,
				 (unsigned)map->m_len, -err);
			ext4_msg(sb, KERN_CRIT,
				 "This should not happen!! Data will "
				 "be lost\n");
			if (err == -ENOSPC)
				ext4_print_free_blocks(inode);
		invalidate_dirty_pages:
			*give_up_on_write = true;
			return err;
		}
		progress = 1;
		/*
		 * 更新緩沖區狀態,提交映射頁面,并為我們獲取新的映射范圍
		 */
		err = mpage_map_and_submit_buffers(mpd);
		if (err < 0)
			goto update_disksize;
	} while (map->m_len);

update_disksize:
	/*
	 * IO 提交后更新磁盤大小, 通過檢查 i_data_sem 下的 i_size 避免了 truncate 的競爭,
	 */
	disksize = ((loff_t)mpd->first_page) << PAGE_SHIFT;
	if (disksize > READ_ONCE(EXT4_I(inode)->i_disksize)) {
		int err2;
		loff_t i_size;

		down_write(&EXT4_I(inode)->i_data_sem);
		i_size = i_size_read(inode);
		if (disksize > i_size)
			disksize = i_size;
		if (disksize > EXT4_I(inode)->i_disksize)
			EXT4_I(inode)->i_disksize = disksize;
		up_write(&EXT4_I(inode)->i_data_sem);
		err2 = ext4_mark_inode_dirty(handle, inode);
		if (err2) {
			ext4_error_err(inode->i_sb, -err2,
				       "Failed to mark inode %lu dirty",
				       inode->i_ino);
		}
		if (!err)
			err = err2;
	}
	return err;
}

/*
 * 計算為一次 writepages 迭代保留的積分總數, 這是從 ext4_writepages() 呼叫的, 
 *我們最多映射 MAX_WRITEPAGES_EXTENT_LEN 塊的范圍,然后我們繼續完成最后部分頁面的映射, 
 *所以總的來說我們可以在 bpp 不同的范圍內映射 MAX_WRITEPAGES_EXTENT_LEN + bpp - 1 個塊,
 */
static int ext4_da_writepages_trans_blocks(struct inode *inode)
{
	int bpp = ext4_journal_blocks_per_page(inode);

	return ext4_meta_trans_blocks(inode,
				MAX_WRITEPAGES_EXTENT_LEN + bpp - 1, bpp);
}

/*
  * mpage_prepare_extent_to_map - 查找并鎖定連續范圍的臟頁和要映射的底層范圍
  *
  * @mpd - 在哪里尋找頁面
  *
  * 在映射中走臟頁, 如果它們已完全映射,請立即將它們提交給 IO, 
    當我們找到一個未被映射的頁面時,我們開始累積這些頁面下需要映射的緩沖區的范圍(由延遲或未寫入的緩沖區形成), 
	我們還鎖定包含這些緩沖區的頁面, 找到的范圍在@mpd 結構中回傳(從 mpd->lblk 開始,長度為 mpd->len 塊),
  *
  * 請注意,此函式可以將 bios 附加到一個 io_end 結構上,該結構在邏輯上和物理上都不連續, 
    盡管這似乎是一種不必要的復雜化,但在塊大小 < 頁大小的情況下實際上是不可避免的,因為我們需要在一個 io_end 中跟蹤到頁面下所有緩沖區的 IO,
 */
static int mpage_prepare_extent_to_map(struct mpage_da_data *mpd)
{
	struct address_space *mapping = mpd->inode->i_mapping;
	struct pagevec pvec;
	unsigned int nr_pages;
	long left = mpd->wbc->nr_to_write;
	pgoff_t index = mpd->first_page;
	pgoff_t end = mpd->last_page;
	xa_mark_t tag;
	int i, err = 0;
	int blkbits = mpd->inode->i_blkbits;
	ext4_lblk_t lblk;
	struct buffer_head *head;

	if (mpd->wbc->sync_mode == WB_SYNC_ALL || mpd->wbc->tagged_writepages)
		tag = PAGECACHE_TAG_TOWRITE;
	else
		tag = PAGECACHE_TAG_DIRTY;

	pagevec_init(&pvec);
	mpd->map.m_len = 0;
	mpd->next_page = index;
	while (index <= end) {
		nr_pages = pagevec_lookup_range_tag(&pvec, mapping, &index, end,
				tag);
		if (nr_pages == 0)
			break;

		for (i = 0; i < nr_pages; i++) {
			struct page *page = pvec.pages[i];

			/*
			 * 積累了足夠多的臟頁? 這不適用于 WB_SYNC_ALL 模式, 
			   對于完整性同步,我們必須繼續進行,因為有人可能同時在弄臟頁面,
			   我們可能同步了很多新出現的臟頁,但還沒有同步所有舊的臟頁,
			 */
			if (mpd->wbc->sync_mode == WB_SYNC_NONE && left <= 0)
				goto out;

			/* 如果我們不能合并這個頁面,我們就完成了, */
			if (mpd->map.m_len > 0 && mpd->next_page != page->index)
				goto out;

			lock_page(page);
			/*
			 * 如果頁面不再臟,或者它的映射不再對應我們正在寫入的 inode(這意味著它已經被截斷或失效),
			   或者頁面已經在寫回并且我們沒有做資料完整性寫回,請跳過該頁
			 */
			if (!PageDirty(page) ||
			    (PageWriteback(page) &&
			     (mpd->wbc->sync_mode == WB_SYNC_NONE)) ||
			    unlikely(page->mapping != mapping)) {
				unlock_page(page);
				continue;
			}

			wait_on_page_writeback(page);
			BUG_ON(PageWriteback(page));

			if (mpd->map.m_len == 0)
				mpd->first_page = page->index;
			mpd->next_page = page->index + 1;
			/* 將所有臟緩沖區添加到 mpd */
			lblk = ((ext4_lblk_t)page->index) <<
				(PAGE_SHIFT - blkbits);
			head = page_buffers(page);
			err = mpage_process_page_bufs(mpd, head, head, lblk);
			if (err <= 0)
				goto out;
			err = 0;
			left--;
		}
		pagevec_release(&pvec);
		cond_resched();
	}
	mpd->scanned_until_end = 1;
	return 0;
out:
	pagevec_release(&pvec);
	return err;
}

static int ext4_writepages(struct address_space *mapping,
			   struct writeback_control *wbc)
{
	pgoff_t	writeback_index = 0;
	long nr_to_write = wbc->nr_to_write;
	int range_whole = 0;
	int cycled = 1;
	handle_t *handle = NULL;
	struct mpage_da_data mpd;
	struct inode *inode = mapping->host;
	int needed_blocks, rsv_blocks = 0, ret = 0;
	struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);
	struct blk_plug plug;
	bool give_up_on_write = false;

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
		return -EIO;

	percpu_down_read(&sbi->s_writepages_rwsem);
	trace_ext4_writepages(inode, wbc);

	/*
	 * 沒有頁面可寫? 這主要是為了避免在最后的 iput() 上為特殊的 inode 啟動事務,
	   例如日志 inode,因為這可能會違反 umount 上的鎖順序
	 */
	if (!mapping->nrpages || !mapping_tagged(mapping, PAGECACHE_TAG_DIRTY))
		goto out_writepages;

	if (ext4_should_journal_data(inode)) {
		ret = generic_writepages(mapping, wbc);
		goto out_writepages;
	}

	/*
	 * 如果檔案系統已中止,則它是只讀的,因此立即回傳而不是稍后轉儲堆疊跟蹤,這將掩蓋問題的真正根源, 
	   我們測驗 EXT4_MF_FS_ABORTED 而不是 sb->s_flag 的 SB_RDONLY,因為如果檔案系統以只讀方式掛載,后者可能為真,
	   在這種情況下,不應呼叫 ext4_writepages,因此如果發生這種情況,我們將需要堆疊跟蹤,
	 */
	if (unlikely(ext4_forced_shutdown(EXT4_SB(mapping->host->i_sb)) ||
		     ext4_test_mount_flag(inode->i_sb, EXT4_MF_FS_ABORTED))) {
		ret = -EROFS;
		goto out_writepages;
	}

	/*
	 * 如果我們有行內資料到達這里,意味著我們很快就會為第一頁創建塊,所以我們最好在這里清除行內資料,
	 */
	if (ext4_has_inline_data(inode)) {
		/* Just inode will be modified... */
		handle = ext4_journal_start(inode, EXT4_HT_INODE, 1);
		if (IS_ERR(handle)) {
			ret = PTR_ERR(handle);
			goto out_writepages;
		}
		BUG_ON(ext4_test_inode_state(inode,
				EXT4_STATE_MAY_INLINE_DATA));
		ext4_destroy_inline_data(handle, inode);
		ext4_journal_stop(handle);
	}

	if (ext4_should_dioread_nolock(inode)) {
		/*
		 * 我們可能需要為頁面中的每個塊最多轉換一個范圍,并且我們可能會弄臟 inode,
		 */
		rsv_blocks = 1 + ext4_chunk_trans_blocks(inode,
						PAGE_SIZE >> inode->i_blkbits);
	}

	if (wbc->range_start == 0 && wbc->range_end == LLONG_MAX)
		range_whole = 1;

	if (wbc->range_cyclic) {
		writeback_index = mapping->writeback_index;
		if (writeback_index)
			cycled = 0;
		mpd.first_page = writeback_index;
		mpd.last_page = -1;
	} else {
		mpd.first_page = wbc->range_start >> PAGE_SHIFT;
		mpd.last_page = wbc->range_end >> PAGE_SHIFT;
	}

	mpd.inode = inode;
	mpd.wbc = wbc;
	ext4_io_submit_init(&mpd.io_submit, wbc);
retry:
	if (wbc->sync_mode == WB_SYNC_ALL || wbc->tagged_writepages)
		tag_pages_for_writeback(mapping, mpd.first_page, mpd.last_page);
	blk_start_plug(&plug);

	/*
	 * 不需要映射的第一個回寫頁面 - 我們可以避免不必要地啟動事務,也可以避免在事務啟動時因設備擁塞而在塊層中被阻塞,
	 */
	mpd.do_map = 0;
	mpd.scanned_until_end = 0;
	mpd.io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
	if (!mpd.io_submit.io_end) {
		ret = -ENOMEM;
		goto unplug;
	}
	ret = mpage_prepare_extent_to_map(&mpd);
	/* 解鎖我們沒有使用的頁面 */
	mpage_release_unused_pages(&mpd, false);
	/* 提交準備好的bio */
	ext4_io_submit(&mpd.io_submit);
	ext4_put_io_end_defer(mpd.io_submit.io_end);
	mpd.io_submit.io_end = NULL;
	if (ret < 0)
		goto unplug;

	while (!mpd.scanned_until_end && wbc->nr_to_write > 0) {
		/* 對于每個頁面范圍,我們使用新的 io_end */
		mpd.io_submit.io_end = ext4_init_io_end(inode, GFP_KERNEL);
		if (!mpd.io_submit.io_end) {
			ret = -ENOMEM;
			break;
		}

		/*
		 * 我們有兩個約束:我們找到一個范圍來映射,我們必須總是寫出整個頁面(當blocksize < pagesize 時會有所不同),
		   這樣當我們嘗試寫出頁面的其余部分時我們不會阻塞IO, delalloc 不支持日志模式,
		 */
		BUG_ON(ext4_should_journal_data(inode));
		needed_blocks = ext4_da_writepages_trans_blocks(inode);

		/* 開始一個新的事務 */
		handle = ext4_journal_start_with_reserve(inode,
				EXT4_HT_WRITE_PAGE, needed_blocks, rsv_blocks);
		if (IS_ERR(handle)) {
			ret = PTR_ERR(handle);
			ext4_msg(inode->i_sb, KERN_CRIT, "%s: jbd2_start: "
			       "%ld pages, ino %lu; err %d", __func__,
				wbc->nr_to_write, inode->i_ino, ret);
			/* 釋放分配的 io_end */
			ext4_put_io_end(mpd.io_submit.io_end);
			mpd.io_submit.io_end = NULL;
			break;
		}
		mpd.do_map = 1;

		trace_ext4_da_write_pages(inode, mpd.first_page, mpd.wbc);
		ret = mpage_prepare_extent_to_map(&mpd);
		if (!ret && mpd.map.m_len)
			ret = mpage_map_and_submit_extent(handle, &mpd,
					&give_up_on_write);
		/*
		 * 注意:如果句柄是同步的,ext4_journal_stop() 可以等待事務提交完成,這可能取決于頁面的回寫完成或頁面鎖的釋放, 
		   在這種情況下,我們必須等到提交所有 IO、釋放我們持有的頁面鎖并洗掉 io_end 參考(為了能夠完成范圍轉換)之后,才能停止句柄,
		 */
		if (!ext4_handle_valid(handle) || handle->h_sync == 0) {
			ext4_journal_stop(handle);
			handle = NULL;
			mpd.do_map = 0;
		}
		/* 解鎖我們沒有使用的頁面 */
		mpage_release_unused_pages(&mpd, give_up_on_write);
		/* 提交準備好的bio */
		ext4_io_submit(&mpd.io_submit);

		/*
		 * 洗掉我們從 init 獲得的 io_end 參考, 如果我們仍然持有交易,我們必須小心并使用延遲的 io_end 完成,
		   因為我們可以釋放對 io_end 的最后一個參考,這可能最侄訓進行未寫的范圍轉換,
		 */
		if (handle) {
			ext4_put_io_end_defer(mpd.io_submit.io_end);
			ext4_journal_stop(handle);
		} else
			ext4_put_io_end(mpd.io_submit.io_end);
		mpd.io_submit.io_end = NULL;

		if (ret == -ENOSPC && sbi->s_journal) {
			/*
			 * 提交將釋放交易中釋放的塊的交易并重試
			 */
			jbd2_journal_force_commit_nested(sbi->s_journal);
			ret = 0;
			continue;
		}
		/* Fatal error - ENOMEM, EIO... */
		if (ret)
			break;
	}
unplug:
	blk_finish_plug(&plug);
	if (!ret && !cycled && wbc->nr_to_write > 0) {
		cycled = 1;
		mpd.last_page = writeback_index - 1;
		mpd.first_page = 0;
		goto retry;
	}

	/* 更新索引 */
	if (wbc->range_cyclic || (range_whole && wbc->nr_to_write > 0))
		/*
		 * 設定 writeback_index 以便 range_cyclic 模式稍后將其寫回
		 */
		mapping->writeback_index = mpd.first_page;

out_writepages:
	trace_ext4_writepages_result(inode, wbc, ret,
				     nr_to_write - wbc->nr_to_write);
	percpu_up_read(&sbi->s_writepages_rwsem);
	return ret;
}

static int ext4_dax_writepages(struct address_space *mapping,
			       struct writeback_control *wbc)
{
	int ret;
	long nr_to_write = wbc->nr_to_write;
	struct inode *inode = mapping->host;
	struct ext4_sb_info *sbi = EXT4_SB(mapping->host->i_sb);

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
		return -EIO;

	percpu_down_read(&sbi->s_writepages_rwsem);
	trace_ext4_writepages(inode, wbc);

	ret = dax_writeback_mapping_range(mapping, sbi->s_daxdev, wbc);
	trace_ext4_writepages_result(inode, wbc, ret,
				     nr_to_write - wbc->nr_to_write);
	percpu_up_read(&sbi->s_writepages_rwsem);
	return ret;
}

static int ext4_nonda_switch(struct super_block *sb)
{
	s64 free_clusters, dirty_clusters;
	struct ext4_sb_info *sbi = EXT4_SB(sb);

	/*
	 * 如果我們在空閑塊上運行不足,則切換到非 delalloc 模式, percpu_counter_batch 在每個 CPU 上累積而不更新全域計數器時,通過 percpu 計數器進行的空閑塊記帳可能會略有錯誤
	 * Delalloc 需要一個準確的空閑塊記帳, 所以當我們接近錯誤范圍時切換到非 delalloc,
	 */
	free_clusters =
		percpu_counter_read_positive(&sbi->s_freeclusters_counter);
	dirty_clusters =
		percpu_counter_read_positive(&sbi->s_dirtyclusters_counter);
	/*
	 * 當 1/2 的空閑塊是臟的時,開始推送 delalloc,
	 */
	if (dirty_clusters && (free_clusters < 2 * dirty_clusters))
		try_to_writeback_inodes_sb(sb, WB_REASON_FS_FREE_SPACE);

	if (2 * free_clusters < 3 * dirty_clusters ||
	    free_clusters < (dirty_clusters + EXT4_FREECLUSTERS_WATERMARK)) {
		/*
		 * 空閑塊數小于臟塊的 150% 或空閑塊小于水印
		 */
		return 1;
	}
	return 0;
}

/* 我們總是為 inode 更新保留; 超級塊也可能在那里 */
static int ext4_da_write_credits(struct inode *inode, loff_t pos, unsigned len)
{
	if (likely(ext4_has_feature_large_file(inode->i_sb)))
		return 1;

	if (pos + len <= 0x7fffffffULL)
		return 1;

	/* 我們可能需要更新超級塊來設定 LARGE_FILE */
	return 2;
}

static int ext4_da_write_begin(struct file *file, struct address_space *mapping,
			       loff_t pos, unsigned len, unsigned flags,
			       struct page **pagep, void **fsdata)
{
	int ret, retries = 0;
	struct page *page;
	pgoff_t index;
	struct inode *inode = mapping->host;
	handle_t *handle;

	if (unlikely(ext4_forced_shutdown(EXT4_SB(inode->i_sb))))
		return -EIO;

	index = pos >> PAGE_SHIFT;

	if (ext4_nonda_switch(inode->i_sb) || S_ISLNK(inode->i_mode) ||
	    ext4_verity_in_progress(inode)) {
		*fsdata = (void *)FALL_BACK_TO_NONDELALLOC;
		return ext4_write_begin(file, mapping, pos,
					len, flags, pagep, fsdata);
	}
	*fsdata = (void *)0;
	trace_ext4_da_write_begin(inode, pos, len, flags);

	if (ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) {
		ret = ext4_da_write_inline_data_begin(mapping, inode,
						      pos, len, flags,
						      pagep, fsdata);
		if (ret < 0)
			return ret;
		if (ret == 1)
			return 0;
	}

	/*
	 * 如果系統由于記憶體壓力而抖動,或者頁面正在被寫回,grab_cache_page_write_begin() 可能需要很長時間, 
	   所以在我們開始事務句柄之前先抓住它, 這也允許我們在不使用 GFP_NOFS 的情況下分配頁面(如果需要),
	 */
retry_grab:
	page = grab_cache_page_write_begin(mapping, index, flags);
	if (!page)
		return -ENOMEM;
	unlock_page(page);

	/*
	 * 使用延遲分配,如果有延遲塊分配,我們不會記錄 i_disksize 更新, 但是,如果寫入已映射緩沖區的檔案末尾,我們仍然需要記錄 i_disksize 更新,
	 */
retry_journal:
	handle = ext4_journal_start(inode, EXT4_HT_WRITE_PAGE,
				ext4_da_write_credits(inode, pos, len));
	if (IS_ERR(handle)) {
		put_page(page);
		return PTR_ERR(handle);
	}

	lock_page(page);
	if (page->mapping != mapping) {
		/* 頁面從我們下面被截斷 */
		unlock_page(page);
		put_page(page);
		ext4_journal_stop(handle);
		goto retry_grab;
	}
	/* 如果在頁面解鎖時開始寫回 */
	wait_for_stable_page(page);

#ifdef CONFIG_FS_ENCRYPTION
	ret = ext4_block_write_begin(page, pos, len,
				     ext4_da_get_block_prep);
#else
	ret = __block_write_begin(page, pos, len, ext4_da_get_block_prep);
#endif
	if (ret < 0) {
		unlock_page(page);
		ext4_journal_stop(handle);
		/*
		 * block_write_begin 可能在 i_size 之外實體化了幾個塊, 再把這些剪掉, 不需要 i_size_read 因為我們持有 i_mutex,
		 */
		if (pos + len > inode->i_size)
			ext4_truncate_failed_write(inode);

		if (ret == -ENOSPC &&
		    ext4_should_retry_alloc(inode->i_sb, &retries))
			goto retry_journal;

		put_page(page);
		return ret;
	}

	*pagep = page;
	return ret;
}

/*
 * 檢查我們是否應該在寫入檔案末尾但不需要塊分配時更新 i_disksize
 */
static int ext4_da_should_update_i_disksize(struct page *page,
					    unsigned long offset)
{
	struct buffer_head *bh;
	struct inode *inode = page->mapping->host;
	unsigned int idx;
	int i;

	bh = page_buffers(page);
	idx = offset >> inode->i_blkbits;

	for (i = 0; i < idx; i++)
		bh = bh->b_this_page;

	if (!buffer_mapped(bh) || (buffer_delay(bh)) || buffer_unwritten(bh))
		return 0;
	return 1;
}

static int ext4_da_write_end(struct file *file,
			     struct address_space *mapping,
			     loff_t pos, unsigned len, unsigned copied,
			     struct page *page, void *fsdata)
{
	struct inode *inode = mapping->host;
	int ret = 0, ret2;
	handle_t *handle = ext4_journal_current_handle();
	loff_t new_i_size;
	unsigned long start, end;
	int write_mode = (int)(unsigned long)fsdata;

	if (write_mode == FALL_BACK_TO_NONDELALLOC)
		return ext4_write_end(file, mapping, pos,
				      len, copied, page, fsdata);

	trace_ext4_da_write_end(inode, pos, len, copied);
	start = pos & (PAGE_SIZE - 1);
	end = start + copied - 1;

	/*
	 * 如果 i_size 改變, generic_write_end() 將運行 mark_inode_dirty(), 
	   因此,讓我們將 i_disksize mark_inode_dirty 附加到其中,
	 */
	new_i_size = pos + copied;
	if (copied && new_i_size > EXT4_I(inode)->i_disksize) {
		if (ext4_has_inline_data(inode) ||
		    ext4_da_should_update_i_disksize(page, end)) {
			ext4_update_i_disksize(inode, new_i_size);
			/* 
			 即使 new_i_size 小于 inode->i_size bu 大于 i_disksize,我們也需要將 inode 標記為臟,(提示 delalloc)
			 */
			ret = ext4_mark_inode_dirty(handle, inode);
		}
	}

	if (write_mode != CONVERT_INLINE_DATA &&
	    ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA) &&
	    ext4_has_inline_data(inode))
		ret2 = ext4_da_write_inline_data_end(inode, pos, len, copied,
						     page);
	else
		ret2 = generic_write_end(file, mapping, pos, len, copied,
							page, fsdata);

	copied = ret2;
	if (ret2 < 0)
		ret = ret2;
	ret2 = ext4_journal_stop(handle);
	if (unlikely(ret2 && !ret))
		ret = ret2;

	return ret ? ret : copied;
}

未完待續……

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/387135.html

標籤:其他

上一篇:idea2021 試用 Mac&Windows通用【支持正版IntelliJ IDEA】

下一篇:【資料結構和演算法筆記】選擇排序(簡單選擇排序,二路歸并排序)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more