patch-1.3.54 linux/mm/filemap.c

Next file: linux/mm/memory.c
Previous file: linux/kernel/printk.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v1.3.53/linux/mm/filemap.c linux/mm/filemap.c
@@ -84,9 +84,9 @@
 	while (priority-- > 0) {
 		if (page->inode) {
 			unsigned age = page->age;
-			/* if the page is shared, we juvenate it slightly */
+			/* if the page is shared, we juvenate it */
 			if (page->count != 1)
-				age |= PAGE_AGE_VALUE;
+				age |= PAGE_AGE_VALUE << 1;
 			page->age = age >> 1;
 			if (age <= PAGE_AGE_VALUE/2) {
 				remove_page_from_hash_queue(page);
@@ -127,28 +127,174 @@
 }
 
 /*
- * This should be a low-level fs-specific function (ie
- * inode->i_op->readpage).
+ * Update a page cache copy, when we're doing a "write()" system call
+ * See also "update_vm_cache()".
  */
-static int readpage(struct inode * inode, unsigned long offset, char * page)
+void update_vm_cache(struct inode * inode, unsigned long pos, const char * buf, int count)
 {
-	int *p, nr[PAGE_SIZE/512];
-	int i;
+	struct page * page;
 
-	i = PAGE_SIZE >> inode->i_sb->s_blocksize_bits;
-	offset >>= inode->i_sb->s_blocksize_bits;
-	p = nr;
-	do {
-		*p = inode->i_op->bmap(inode, offset);
-		i--;
-		offset++;
-		p++;
-	} while (i > 0);
-	bread_page((unsigned long) page, inode->i_dev, nr, inode->i_sb->s_blocksize);
+	page = find_page(inode, pos & PAGE_MASK);
+	if (page) {
+		unsigned long addr;
+
+		page->count++;
+		if (!page->uptodate)
+			sleep_on(&page->wait);
+		addr = page_address(page);
+		memcpy((void *) ((pos & ~PAGE_MASK) + addr), buf, count);
+		free_page(addr);
+	}
+}
+
+/*
+ * Find a cached page and wait for it to become up-to-date, return
+ * the page address.
+ *
+ * If no cached page can be found, create one using the supplied
+ * new page instead (and return zero to indicate that we used the
+ * supplied page in doing so).
+ */
+static unsigned long fill_page(struct inode * inode, unsigned long offset, unsigned long newpage)
+{
+	struct page * page;
+
+	page = find_page(inode, offset);
+	if (page) {
+		if (!page->uptodate)
+			sleep_on(&page->wait);
+		return page_address(page);
+	}
+	page = mem_map + MAP_NR(newpage);
+	page->count++;
+	page->uptodate = 0;
+	page->error = 0;
+	page->offset = offset;
+	add_page_to_inode_queue(inode, page);
+	add_page_to_hash_queue(inode, page);
+	inode->i_op->readpage(inode, page);
+	page->uptodate = 1;
+	wake_up(&page->wait);
 	return 0;
 }
 
 /*
+ * Try to read ahead in the file. "page_cache" is a potentially free page
+ * that we could use for the cache (if it is 0 we can try to create one,
+ * this is all overlapped with the IO on the previous page finishing anyway)
+ */
+static unsigned long try_to_read_ahead(struct inode * inode, unsigned long offset, unsigned long page_cache)
+{
+	if (!page_cache)
+		page_cache = __get_free_page(GFP_KERNEL);
+	offset = (offset + PAGE_SIZE) & PAGE_MASK;
+	/*
+	 * read-ahead is not implemented yet, but this is
+	 * where we should start..
+	 */
+	return page_cache;
+}
+
+/*
+ * This is a generic file read routine, and uses the
+ * inode->i_op->readpage() function for the actual low-level
+ * stuff.
+ */
+int generic_file_read(struct inode * inode, struct file * filp, char * buf, int count)
+{
+	int read = 0;
+	unsigned long pos;
+	unsigned long page_cache = 0;
+
+	if (count <= 0)
+		return 0;
+
+	pos = filp->f_pos;
+	do {
+		struct page *page;
+		unsigned long offset, addr, nr;
+
+		if (pos >= inode->i_size)
+			break;
+		offset = pos & ~PAGE_MASK;
+		nr = PAGE_SIZE - offset;
+		/*
+		 * Try to find the data in the page cache..
+		 */
+		page = find_page(inode, pos & PAGE_MASK);
+		if (page)
+			goto found_page;
+
+		/*
+		 * Ok, it wasn't cached, so we need to create a new
+		 * page..
+		 */
+		if (!page_cache) {
+			page_cache = __get_free_page(GFP_KERNEL);
+			if (!page_cache) {
+				if (!read)
+					read = -ENOMEM;
+				break;
+			}
+		}
+
+		/*
+		 * That could have slept, so we need to check again..
+		 */
+		if (pos >= inode->i_size)
+			break;
+		page = find_page(inode, pos & PAGE_MASK);
+		if (page)
+			goto found_page;
+
+		/*
+		 * Ok, add the new page to the hash-queues...
+		 */
+		page = mem_map + MAP_NR(page_cache);
+		page_cache = 0;
+		page->count++;
+		page->uptodate = 0;
+		page->error = 0;
+		page->offset = pos & PAGE_MASK;
+		add_page_to_inode_queue(inode, page);
+		add_page_to_hash_queue(inode, page);
+
+		/* 
+		 * And start IO on it..
+		 * (this should be asynchronous, but currently isn't)
+		 */
+		inode->i_op->readpage(inode, page);
+
+found_page:
+		addr = page_address(page);
+		if (nr > count)
+			nr = count;
+		if (!page->uptodate) {
+			page_cache = try_to_read_ahead(inode, offset, page_cache);
+			if (!page->uptodate)
+				sleep_on(&page->wait);
+		}
+		if (nr > inode->i_size - pos)
+			nr = inode->i_size - pos;
+		memcpy_tofs(buf, (void *) (addr + offset), nr);
+		free_page(addr);
+		buf += nr;
+		pos += nr;
+		read += nr;
+		count -= nr;
+	} while (count);
+
+	filp->f_pos = pos;
+	if (page_cache)
+		free_page(page_cache);
+	if (!IS_RDONLY(inode)) {
+		inode->i_atime = CURRENT_TIME;
+		inode->i_dirt = 1;
+	}
+	return read;
+}
+
+/*
  * Semantics for shared and private memory areas are different past the end
  * of the file. A shared mapping past the last page of the file is an error
  * and results in a SIBGUS, while a private mapping just maps in a zero page.
@@ -156,51 +302,36 @@
 static unsigned long filemap_nopage(struct vm_area_struct * area, unsigned long address,
 	unsigned long page, int no_share)
 {
+	unsigned long offset;
 	struct inode * inode = area->vm_inode;
-	unsigned long new_page, old_page;
-	struct page *p;
+	unsigned long new_page;
 
-	address = (address & PAGE_MASK) - area->vm_start + area->vm_offset;
-	if (address >= inode->i_size && (area->vm_flags & VM_SHARED) && area->vm_mm == current->mm)
+	offset = (address & PAGE_MASK) - area->vm_start + area->vm_offset;
+	if (offset >= inode->i_size && (area->vm_flags & VM_SHARED) && area->vm_mm == current->mm)
 		send_sig(SIGBUS, current, 1);
-	p = find_page(inode, address);
-	if (p)
-		goto old_page_exists;
-	new_page = 0;
+
+	new_page = fill_page(inode, offset, page);
+	if (new_page) {
+		if (no_share) {
+			memcpy((void *) page, (void *) new_page, PAGE_SIZE);
+			free_page(new_page);
+			return page;
+		}
+		free_page(page);
+		return new_page;
+	}
+
 	if (no_share) {
 		new_page = __get_free_page(GFP_USER);
 		if (!new_page) {
 			oom(current);
-			return page;
+			new_page = pte_page(BAD_PAGE);
 		}
-	}
-	/* inode->i_op-> */ readpage(inode, address, (char *) page);
-	p = find_page(inode, address);
-	if (p)
-		goto old_and_new_page_exists;
-	p = mem_map + MAP_NR(page);
-	p->offset = address;
-	add_page_to_inode_queue(inode, p);
-	add_page_to_hash_queue(inode, p);
-	if (new_page) {
 		memcpy((void *) new_page, (void *) page, PAGE_SIZE);
-		return new_page;
+		free_page(page);
+		page = new_page;
 	}
-	p->count++;
 	return page;
-
-old_and_new_page_exists:
-	if (new_page)
-		free_page(new_page);
-old_page_exists:
-	old_page = page_address(p);
-	if (no_share) {
-		memcpy((void *) page, (void *) old_page, PAGE_SIZE);
-		return page;
-	}
-	p->count++;
-	free_page(page);
-	return old_page;
 }
 
 /*
@@ -479,7 +610,7 @@
 	}
 	if (!inode->i_sb || !S_ISREG(inode->i_mode))
 		return -EACCES;
-	if (!inode->i_op || !inode->i_op->bmap)
+	if (!inode->i_op || !inode->i_op->readpage)
 		return -ENOEXEC;
 	if (!IS_RDONLY(inode)) {
 		inode->i_atime = CURRENT_TIME;

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov with Sam's (original) version
of this