| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "net/disk_cache/mapped_file.h" | 5 #include "net/disk_cache/mapped_file.h" |
| 6 | 6 |
| 7 #include <stdlib.h> | 7 #include <stdlib.h> |
| 8 #include <sys/mman.h> | |
| 9 | |
| 10 #include <map> | |
| 11 | 8 |
| 12 #include "base/files/file_path.h" | 9 #include "base/files/file_path.h" |
| 13 #include "base/lazy_instance.h" | |
| 14 #include "base/logging.h" | 10 #include "base/logging.h" |
| 15 #include "base/memory/scoped_ptr.h" | |
| 16 #include "base/memory/scoped_vector.h" | |
| 17 #include "base/threading/thread_local.h" | |
| 18 | |
| 19 // This implementation of MappedFile doesn't use a shared RW mmap for | |
| 20 // performance reason. Instead it will use a private RO mmap and install a SEGV | |
| 21 // signal handler. When the memory is modified, the handler will register the | |
| 22 // modified address and change the protection of the page to RW. When a flush is | |
| 23 // then executed, it has access to the exact pages that have been modified and | |
| 24 // will only write those to disk. The handler will only track half of the dirty | |
| 25 // pages. If more than half the pages are modified, the flush will instead write | |
| 26 // the full buffer to disk. | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 // Choose 4k as a reasonable page size. As this file is used mainly on Android, | |
| 31 // this is the real android page size. | |
| 32 const size_t kPageSize = 4096; | |
| 33 | |
| 34 // Variable capacity array, optimized for capacity of 1. Most of the mapped file | |
| 35 // are used to map exactly 2 pages. Tracking 1 page is then optimal because if | |
| 36 // both pages are modified, writing the full view is the optimal behavior. | |
| 37 class SmallArray { | |
| 38 public: | |
| 39 SmallArray() : capacity_(0), array_(NULL) {} | |
| 40 ~SmallArray() { SetCapacity(0); } | |
| 41 | |
| 42 size_t capacity() { return capacity_; } | |
| 43 char** array() { return array_; } | |
| 44 void SetCapacity(size_t capacity) { | |
| 45 if (capacity_ > 1) | |
| 46 delete[] array_; | |
| 47 capacity_ = capacity; | |
| 48 if (capacity > 1) | |
| 49 array_ = new char*[capacity]; | |
| 50 else | |
| 51 array_ = small_array_; | |
| 52 } | |
| 53 | |
| 54 private: | |
| 55 size_t capacity_; | |
| 56 char** array_; | |
| 57 char* small_array_[1]; | |
| 58 }; | |
| 59 | |
| 60 // Information about the memory mapped part of a file. | |
| 61 struct MappedFileInfo { | |
| 62 // Stat address of the memory. | |
| 63 char* start_address; | |
| 64 // Size of the memory map. | |
| 65 size_t size; | |
| 66 // Number of dirty page. A page is dirty if the memory content is different | |
| 67 // from the file content. | |
| 68 size_t num_dirty_pages; | |
| 69 // The dirty pages. | |
| 70 SmallArray dirty_pages; | |
| 71 }; | |
| 72 | |
| 73 // The maximum number of dirty pages that can be tracked. Limit the memory | |
| 74 // overhead to 2kb per file. | |
| 75 const size_t kMaxDirtyPagesCacheSize = | |
| 76 kPageSize / sizeof(char*) / 2 - sizeof(MappedFileInfo); | |
| 77 | |
| 78 class ThreadLocalMappedFileInfo { | |
| 79 public: | |
| 80 ThreadLocalMappedFileInfo() {} | |
| 81 ~ThreadLocalMappedFileInfo() {} | |
| 82 | |
| 83 void RegisterMappedFile(disk_cache::MappedFile* mapped_file, size_t size) { | |
| 84 scoped_ptr<MappedFileInfo> new_info(new MappedFileInfo); | |
| 85 new_info->start_address = static_cast<char*>(mapped_file->buffer()); | |
| 86 new_info->size = size; | |
| 87 new_info->num_dirty_pages = 0; | |
| 88 // Track half of the dirty pages, after this, just overwrite the full | |
| 89 // content. | |
| 90 size_t capacity = (size + kPageSize - 1) / kPageSize / 2; | |
| 91 if (capacity > kMaxDirtyPagesCacheSize) | |
| 92 capacity = kMaxDirtyPagesCacheSize; | |
| 93 new_info->dirty_pages.SetCapacity(capacity); | |
| 94 info_per_map_file_[mapped_file] = new_info.get(); | |
| 95 infos_.push_back(new_info.release()); | |
| 96 Update(); | |
| 97 } | |
| 98 | |
| 99 void UnregisterMappedFile(disk_cache::MappedFile* mapped_file) { | |
| 100 MappedFileInfo* info = InfoForMappedFile(mapped_file); | |
| 101 DCHECK(info); | |
| 102 info_per_map_file_.erase(mapped_file); | |
| 103 infos_.erase(std::find(infos_.begin(), infos_.end(), info)); | |
| 104 Update(); | |
| 105 } | |
| 106 | |
| 107 MappedFileInfo* InfoForMappedFile(disk_cache::MappedFile* mapped_file) { | |
| 108 return info_per_map_file_[mapped_file]; | |
| 109 } | |
| 110 | |
| 111 MappedFileInfo** infos_ptr() { return infos_ptr_; } | |
| 112 size_t infos_size() { return infos_size_; } | |
| 113 | |
| 114 private: | |
| 115 // Update |infos_ptr_| and |infos_size_| when |infos_| change. | |
| 116 void Update() { | |
| 117 infos_ptr_ = &infos_[0]; | |
| 118 infos_size_ = infos_.size(); | |
| 119 } | |
| 120 | |
| 121 // Link to the MappedFileInfo for a given MappedFile. | |
| 122 std::map<disk_cache::MappedFile*, MappedFileInfo*> info_per_map_file_; | |
| 123 // Vector of information about all current MappedFile belonging to the current | |
| 124 // thread. | |
| 125 ScopedVector<MappedFileInfo> infos_; | |
| 126 // Pointer to the storage part of |infos_|. This is kept as a variable to | |
| 127 // prevent the signal handler from calling any C++ method that might allocate | |
| 128 // memory. | |
| 129 MappedFileInfo** infos_ptr_; | |
| 130 // Size of |infos_|. | |
| 131 size_t infos_size_; | |
| 132 }; | |
| 133 | |
| 134 class SegvHandler { | |
| 135 public: | |
| 136 // Register the signal handler. | |
| 137 SegvHandler(); | |
| 138 ~SegvHandler() {} | |
| 139 | |
| 140 // SEGV signal handler. This handler will check that the address that | |
| 141 // generated the fault is one associated with a mapped file. If that's the | |
| 142 // case, it will register the address and change the protection to RW then | |
| 143 // return. This will cause the instruction that generated the fault to be | |
| 144 // re-executed. If not, it will just reinstall the old handler and return, | |
| 145 // which will generate the fault again and let the initial handler get called. | |
| 146 static void SigSegvHandler(int sig, siginfo_t* si, void* unused); | |
| 147 | |
| 148 base::ThreadLocalPointer<ThreadLocalMappedFileInfo>& thread_local_infos() { | |
| 149 return thread_local_infos_; | |
| 150 } | |
| 151 | |
| 152 private: | |
| 153 // Install the SEGV handler, storing the current sigaction in |old_sigaction| | |
| 154 // if it is not NULL. | |
| 155 static void InstallSigHandler(struct sigaction* old_sigaction); | |
| 156 | |
| 157 base::ThreadLocalPointer<ThreadLocalMappedFileInfo> thread_local_infos_; | |
| 158 struct sigaction old_sigaction_; | |
| 159 }; | |
| 160 | |
| 161 static base::LazyInstance<SegvHandler> g_segv_handler = | |
| 162 LAZY_INSTANCE_INITIALIZER; | |
| 163 | |
| 164 // Initialisation method. | |
| 165 SegvHandler::SegvHandler() { | |
| 166 // Setup the SIGV signal handler. | |
| 167 InstallSigHandler(&old_sigaction_); | |
| 168 } | |
| 169 | |
| 170 // static | |
| 171 void SegvHandler::SigSegvHandler(int sig, siginfo_t* si, void* unused) { | |
| 172 // First, check if the current sighandler has the SA_SIGINFO flag. If it | |
| 173 // doesn't it means an external library installed temporarly a signal handler | |
| 174 // using signal, and so incorrectly restored the current one. The parameters | |
| 175 // are then useless. | |
| 176 struct sigaction current_action; | |
| 177 sigaction(SIGSEGV, NULL, ¤t_action); | |
| 178 if (!(current_action.sa_flags & SA_SIGINFO)) { | |
| 179 LOG(WARNING) << "Signal handler have been re-installed incorrectly."; | |
| 180 InstallSigHandler(NULL); | |
| 181 // Returning will re-run the signal with the correct parameters. | |
| 182 return; | |
| 183 } | |
| 184 ThreadLocalMappedFileInfo* thread_local_info = | |
| 185 g_segv_handler.Pointer()->thread_local_infos().Get(); | |
| 186 if (thread_local_info) { | |
| 187 char* addr = reinterpret_cast<char*>(si->si_addr); | |
| 188 for (size_t i = 0; i < thread_local_info->infos_size(); ++i) { | |
| 189 MappedFileInfo* info = thread_local_info->infos_ptr()[i]; | |
| 190 if (info->start_address <= addr && | |
| 191 addr < info->start_address + info->size) { | |
| 192 // Only track new dirty pages if the array has still some capacity. | |
| 193 // Otherwise, the full buffer will be written to disk and it is not | |
| 194 // necessary to track changes until the next flush. | |
| 195 if (info->num_dirty_pages < info->dirty_pages.capacity()) { | |
| 196 char* aligned_address = reinterpret_cast<char*>( | |
| 197 reinterpret_cast<size_t>(addr) & ~(kPageSize - 1)); | |
| 198 mprotect(aligned_address, kPageSize, PROT_READ | PROT_WRITE); | |
| 199 info->dirty_pages.array()[info->num_dirty_pages] = aligned_address; | |
| 200 } else { | |
| 201 mprotect(info->start_address, info->size, PROT_READ | PROT_WRITE); | |
| 202 } | |
| 203 info->num_dirty_pages++; | |
| 204 return; | |
| 205 } | |
| 206 } | |
| 207 } | |
| 208 // The address it not handled by any mapped filed. Let the default handler get | |
| 209 // called. | |
| 210 sigaction(SIGSEGV, &g_segv_handler.Pointer()->old_sigaction_, NULL); | |
| 211 } | |
| 212 | |
| 213 // static | |
| 214 void SegvHandler::InstallSigHandler(struct sigaction* old_sigaction) { | |
| 215 struct sigaction action; | |
| 216 action.sa_sigaction = SigSegvHandler; | |
| 217 sigemptyset(&action.sa_mask); | |
| 218 action.sa_flags = SA_SIGINFO | SA_RESTART; | |
| 219 sigaction(SIGSEGV, &action, old_sigaction); | |
| 220 } | |
| 221 | |
| 222 } // namespace | |
| 223 | 11 |
| 224 namespace disk_cache { | 12 namespace disk_cache { |
| 225 | 13 |
| 226 void* MappedFile::Init(const base::FilePath& name, size_t size) { | 14 void* MappedFile::Init(const base::FilePath& name, size_t size) { |
| 227 DCHECK(!init_); | 15 DCHECK(!init_); |
| 228 if (init_ || !File::Init(name)) | 16 if (init_ || !File::Init(name)) |
| 229 return NULL; | 17 return NULL; |
| 230 | 18 |
| 231 if (!size) | 19 if (!size) |
| 232 size = GetLength(); | 20 size = GetLength(); |
| 233 | 21 |
| 234 buffer_ = mmap(NULL, size, PROT_READ, MAP_PRIVATE, platform_file(), 0); | 22 buffer_ = malloc(size); |
| 235 if (reinterpret_cast<ptrdiff_t>(buffer_) == -1) { | 23 snapshot_ = malloc(size); |
| 236 NOTREACHED(); | 24 if (buffer_ && snapshot_ && Read(buffer_, size, 0)) { |
| 237 buffer_ = 0; | 25 memcpy(snapshot_, buffer_, size); |
| 238 } | 26 } else { |
| 239 | 27 free(buffer_); |
| 240 if (buffer_) { | 28 free(snapshot_); |
| 241 ThreadLocalMappedFileInfo* thread_local_info = | 29 buffer_ = snapshot_ = 0; |
| 242 g_segv_handler.Pointer()->thread_local_infos().Get(); | |
| 243 if (!thread_local_info) { | |
| 244 thread_local_info = new ThreadLocalMappedFileInfo(); | |
| 245 g_segv_handler.Pointer()->thread_local_infos().Set(thread_local_info); | |
| 246 } | |
| 247 DCHECK(size); | |
| 248 thread_local_info->RegisterMappedFile(this, size); | |
| 249 } | 30 } |
| 250 | 31 |
| 251 init_ = true; | 32 init_ = true; |
| 252 view_size_ = size; | 33 view_size_ = size; |
| 253 return buffer_; | 34 return buffer_; |
| 254 } | 35 } |
| 255 | 36 |
| 256 bool MappedFile::Load(const FileBlock* block) { | 37 bool MappedFile::Load(const FileBlock* block) { |
| 257 size_t offset = block->offset() + view_size_; | 38 size_t offset = block->offset() + view_size_; |
| 258 return Read(block->buffer(), block->size(), offset); | 39 return Read(block->buffer(), block->size(), offset); |
| 259 } | 40 } |
| 260 | 41 |
| 261 bool MappedFile::Store(const FileBlock* block) { | 42 bool MappedFile::Store(const FileBlock* block) { |
| 262 size_t offset = block->offset() + view_size_; | 43 size_t offset = block->offset() + view_size_; |
| 263 return Write(block->buffer(), block->size(), offset); | 44 return Write(block->buffer(), block->size(), offset); |
| 264 } | 45 } |
| 265 | 46 |
| 266 void MappedFile::Flush() { | 47 void MappedFile::Flush() { |
| 267 DCHECK(buffer_); | 48 DCHECK(buffer_); |
| 268 MappedFileInfo* info = g_segv_handler.Pointer()->thread_local_infos().Get()-> | 49 DCHECK(snapshot_); |
| 269 InfoForMappedFile(this); | 50 const char* buffer_ptr = static_cast<const char*>(buffer_); |
| 270 DCHECK(info); | 51 char* snapshot_ptr = static_cast<char*>(snapshot_); |
| 271 if (info->num_dirty_pages > info->dirty_pages.capacity()) { | 52 const size_t block_size = 4096; |
| 272 Write(buffer_, view_size_, 0); | 53 for (size_t offset = 0; offset < view_size_; offset += block_size) { |
| 273 } else { | 54 size_t size = std::min(view_size_ - offset, block_size); |
| 274 const char* buffer_ptr = static_cast<const char*>(buffer_); | 55 if (memcmp(snapshot_ptr + offset, buffer_ptr + offset, size)) { |
| 275 for (size_t i = 0; i < info->num_dirty_pages; ++i) { | 56 memcpy(snapshot_ptr + offset, buffer_ptr + offset, size); |
| 276 const char* ptr = info->dirty_pages.array()[i]; | 57 Write(snapshot_ptr + offset, size, offset); |
| 277 size_t size_to_write = kPageSize; | |
| 278 // The view_size is not a full number of page. Only write the fraction of | |
| 279 // the page that is in the view. | |
| 280 if (ptr - buffer_ptr + kPageSize > view_size_) | |
| 281 size_to_write = view_size_ - (ptr - buffer_ptr); | |
| 282 Write(ptr, size_to_write, ptr - buffer_ptr); | |
| 283 } | 58 } |
| 284 } | 59 } |
| 285 info->num_dirty_pages = 0; | |
| 286 mprotect(buffer_, view_size_, PROT_READ); | |
| 287 } | 60 } |
| 288 | 61 |
| 289 MappedFile::~MappedFile() { | 62 MappedFile::~MappedFile() { |
| 290 if (!init_) | 63 if (!init_) |
| 291 return; | 64 return; |
| 292 | 65 |
| 293 if (buffer_) { | 66 if (buffer_ && snapshot_) { |
| 294 Flush(); | 67 Flush(); |
| 295 ThreadLocalMappedFileInfo* thread_local_info = | |
| 296 g_segv_handler.Pointer()->thread_local_infos().Get(); | |
| 297 DCHECK(thread_local_info); | |
| 298 thread_local_info->UnregisterMappedFile(this); | |
| 299 munmap(buffer_, 0); | |
| 300 } | 68 } |
| 69 free(buffer_); |
| 70 free(snapshot_); |
| 301 } | 71 } |
| 302 | 72 |
| 303 } // namespace disk_cache | 73 } // namespace disk_cache |
| OLD | NEW |