There are two types of virtual addresses that the Linux kernel uses:
- What you already mentioned in the line "Kernel memory (virtual) directly
corresponds to physical memory (just offsetting with 0xC000_0000 will
give us physical address)". This maps to contiguous physical addresses.
- Using vmalloc.
The first one is done using a MACRO:
include/asm-x86/page_32.h
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
_pa(x) does the virtual to physical translation. Please note that this translation happens inline at compile-time. No page-table translation happens. This last sentence is very important.
On the other hand, using the second method you can allocate memory which is contiguous in virtual memory, but may not be so in physical memory. Now, in this case full page table translation is required when you access a virtual address for the first time. The question is who does this?
In the case of CISC machines (like x86), the MMU (hardware) does that in case of a TLB miss (first-time access to virtual address) and updates the page table. For kernel virtual addresses (obtained via vmalloc) they are kept as TLB entries. They are called global entries and when a process context switch occurs, they are mostly ignored and not flushed like the rest of the process-address-space entries. However, when you do a vfree to release the virtual memory associated, those entries are deleted.
In case of a RISC machine (like MIPS), the page translation is handled by software. After a TLB miss, the hardware raise an exception. A trap handle runs in kernel mode to do the translation and updates the TLB using special instructions. After returning from the trap handler the same line of code is run and a TLB hit happens.
Please refer to: http://pages.cs.wisc.edu/~remzi/OSFEP/vm-tlbs.pdf
The bottom-line is that not all kernel addresses are mapped the way you described. For your case, the physical addresses are generated at compile-time itself. So, why add a TLB entry. For addresses from vmalloc, TLB entries are present. When context switch occurs between processes, the entire TLB need not be flushed, and the global entries made by kernel's vmalloc can be preserved. When you use vfree, the corresponding global entries are flushed.