Mit der separaten Datenstruktur meine ich irgendeine Art von struct page, die mehr Informationen zu einer Page enthalten kann als du in einem Pagetable-Eintrag unterbringst. So etwas könntest du eventuell brauchen, sobald du COW implementierst.
Letztendlich besagt diese Aufteilung nur, dass du, wenn du Allokationen für den Kernel machst, diese immer zwischen 0 und 1 GB - 1 haben willst, und wenn sie für den Userspace sind, von 1 GB bis 4 GB. Deine Funktion, die eine virtuelle Adresse alloziert, muss also am besten einen Adressbereich als Parameter bekommen, in dem sie nach freiem Speicher suchen soll.
Dass der Kernel überall gleich gemappt ist, kannst du dadurch lösen, dass in jedem PD die ersten 256 Einträge gleich sind, d.h. du benutzt überall dieselben Pagetables. Wenn du sie in einem Task änderst, hat der andere Task die Änderung automatisch auch. Aufpassen musst du nur, wenn du einen der ersten 256 PD-Einträge änderst: Dann musst du dafür sorgen, dass alle anderen PDs auch auf den aktuellen Stand gebracht werden.