Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

dm-pcache: remove ctrl_lock for pcache_cache_segment

The smatch checker reports a “scheduler in atomic context” problem in
the following call chain:

miss_read_end_req()
-> cache_seg_put()
-> cache_seg_invalidate()
-> cache_seg_gen_increase()
-> mutex_lock(&cache_seg->ctrl_lock);

In practice, this `mutex_lock` will not actually schedule, because it is
only called when `cache_seg_put()` drops the last reference, which is
single-threaded. That is also why the issue never shows up during real
testing.

However, the code is still buggy. The original purpose of `ctrl_lock`
was to prevent read/write conflicts on the cache segment control
information. Looking at the current usage, all control information
accesses are single-threaded: reads only occur during the init phase,
where no conflicts are possible, and writes happen once in the init
phase (also single-threaded) and once when `cache_seg_put()` drops the
last reference (again single-threaded).

Therefore, this patch removes `ctrl_lock` entirely and adds comments in
the appropriate places to document this logic.

Signed-off-by: Dongsheng Yang <dongsheng.yang@linux.dev>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

authored by

Dongsheng Yang and committed by
Mikulas Patocka
1f9ad14a 8d33a030

+17 -6
-1
drivers/md/dm-pcache/cache.h
··· 90 90 u32 gen_index; 91 91 92 92 struct pcache_cache_seg_ctrl *cache_seg_ctrl; 93 - struct mutex ctrl_lock; 94 93 }; 95 94 96 95 /* rbtree for cache entries */
+17 -5
drivers/md/dm-pcache/cache_segment.c
··· 72 72 struct pcache_cache_seg_gen cache_seg_gen, *cache_seg_gen_addr; 73 73 int ret = 0; 74 74 75 - mutex_lock(&cache_seg->ctrl_lock); 76 75 cache_seg_gen_addr = pcache_meta_find_latest(&cache_seg_ctrl->gen->header, 77 76 sizeof(struct pcache_cache_seg_gen), 78 77 sizeof(struct pcache_cache_seg_gen), ··· 92 93 cache_seg->gen_seq = cache_seg_gen.header.seq; 93 94 cache_seg->gen_index = (cache_seg_gen_addr - cache_seg_ctrl->gen); 94 95 out: 95 - mutex_unlock(&cache_seg->ctrl_lock); 96 96 97 97 return ret; 98 98 } ··· 103 105 return (cache_seg_ctrl->gen + cache_seg->gen_index); 104 106 } 105 107 108 + /* 109 + * cache_seg_ctrl_write - write cache segment control information 110 + * @seg: the cache segment to update 111 + * 112 + * This function writes the control information of a cache segment to media. 113 + * 114 + * Although this updates shared control data, we intentionally do not use 115 + * any locking here. All accesses to control information are single-threaded: 116 + * 117 + * - All reads occur during the init phase, where no concurrent writes 118 + * can happen. 119 + * - Writes happen once during init and once when the last reference 120 + * to the segment is dropped in cache_seg_put(). 121 + * 122 + * Both cases are guaranteed to be single-threaded, so there is no risk 123 + * of concurrent read/write races. 124 + */ 106 125 static void cache_seg_ctrl_write(struct pcache_cache_segment *cache_seg) 107 126 { 108 127 struct pcache_cache_seg_gen cache_seg_gen; 109 128 110 - mutex_lock(&cache_seg->ctrl_lock); 111 129 cache_seg_gen.gen = cache_seg->gen; 112 130 cache_seg_gen.header.seq = ++cache_seg->gen_seq; 113 131 cache_seg_gen.header.crc = pcache_meta_crc(&cache_seg_gen.header, ··· 133 119 pmem_wmb(); 134 120 135 121 cache_seg->gen_index = (cache_seg->gen_index + 1) % PCACHE_META_INDEX_MAX; 136 - mutex_unlock(&cache_seg->ctrl_lock); 137 122 } 138 123 139 124 static void cache_seg_ctrl_init(struct pcache_cache_segment *cache_seg) ··· 190 177 spin_lock_init(&cache_seg->gen_lock); 191 178 atomic_set(&cache_seg->refs, 0); 192 179 mutex_init(&cache_seg->info_lock); 193 - mutex_init(&cache_seg->ctrl_lock); 194 180 195 181 /* init pcache_segment */ 196 182 seg_options.type = PCACHE_SEGMENT_TYPE_CACHE_DATA;