在《打印指定进程中的数据》中实现了,在内核中通过日志打印了指定进程,指定地址的字符串数据。这次实现,在一个进程中,打印另一个进程的字符串数据。
第一个测试程序代码如下:(first.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
char* p = "this is first process";
int main()
{
printf("pid = %d\n", getpid());
printf("%d - %p: %s\n", p, p, p);
getchar();
return 0;
}
第二个测试程序代码如下:(second.c)
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
char* p = "this is second process";
int main()
{
printf("pid = %d\n", getpid());
printf("%d: %s\n", p, p);
getchar();
printf("input a address:\n");
int a = 0;
scanf("%d", &a);
printf("%d - %x\n", a, a);
a += 4096; // 字符串在本进程中的实际地址
printf("%d - %x\n", a, a);
// 打印字符串
printf("%s\n", (char*)a);
return 0;
}
要实现在第二个进程中,把第一个进程的字符串“this is first process”打印出来,操作流程是:
一 启动两个测试程序
第一个程序(后面称first)启动后打印日志如下:
pid = 4642
4195892 - 0x400634: this is first process
第二个程序(后面称second)启动后打印日志如下:
pid = 4643
4196164: this is second process
二 执行内核代码
cat /proc/4642/maps,可以查看first的地址空间,前三项如下:
00400000-00401000 r-xp 00000000 08:05 2135623 /home/wang/...
00600000-00601000 rw-p 00000000 08:05 2135623 /home/wang/...
02564000-02585000 rw-p 00000000 00:00 0 [heap]
第一个空间为数据段的地址空间,字符串位于此空间中,大小为一个页框的大小,即4K。second的地址空间也类似,只有一页大小的数据段空间。
/proc/4642/maps中的每一行,在内核中用vm_area_struct结构表示。结构中的vm_start表示地址空间的开始位置,vm_end表示地址空间的结束位置。
要在第二个进程中打印第一个进程的字符串,需要将字符串拷贝到第二个进程中。因此需要增加第二个进程的地址空间,即找到字符串地址所在的地址空间的vm_area_struct,将vm_end后移。但是增加的内存空间,并没有分配实际的物理内存。
现在看下程序分页情况(两个程序都只有一页的数据段,所以以first为例进行讲解):
字符串的地址为0x400634,此字符串存放在一个页框中,字符串地址的低12位(即0x634)为字符串在该页框中的偏移量。PTE和PMD的大小都是4096,在x86_64想系统中,一个地址占8字节,因此一个PMD或PTE可以存放512个地址。上图PAGE的地址就存放在PTE索引为0的位置上。图中的PTE又位于PMD索引为2的位置上。
second数据段增加一页的地址空间后,将first字符串所在的页框地址,放在second程序数据段对应PTE索引为1的位置。所以second.c代码中需要对输入的地址加4096.
a += 4096; // 字符串在本进程中的实际地址
以上是内核代码执行的逻辑。
三 打印字符串
在第二个进程中按“回车”键,输入字符串在第一个进程中的地址(即4195892),按“回车”键。
第二个进程最终的执行结构如下:
pid = 4643
4196164: this is second processinput a address:
4195892
4195892 - 400634
4199988 - 401634
this is first process
上面最后一行,已标红,就是第一个进程中的字符串。
最后附上内核部分代码:
static int hello_open(struct inode* inode, struct file*filep)
{
printk("test begin\n");
struct task_struct * tsk;
int f_pid = 4642; // 第一个进程的pid
int s_pid = 4643; // 第二个进程的pid
pte_t* f_pte = NULL;
pte_t* s_pte = NULL;
for_each_process(tsk) {
if (f_pid == tsk->pid || s_pid == tsk->pid)
{
// 目标字符串的地址
unsigned long addr = 4195892;
struct mm_struct *mm = tsk->mm;
pgd_t *pgd = pgd_offset(mm, addr);
// p4d和pgd是一样的?
p4d_t *p4d = p4d_offset(pgd, addr);
pud_t *pud = pud_offset(p4d, addr);
pmd_t *pmd = pmd_offset(pud, addr);
pte_t *pte = pte_offset_kernel(pmd, addr); // 页表的线性地址
if (f_pid == tsk->pid)
{
f_pte = pte;
}
else if (s_pid == tsk->pid)
{
s_pte = pte;
}
struct vm_area_struct *vm_area = find_vma(mm, addr);
if (NULL != vm_area){
printk("vm_area_struct vm_start = %d\n", vm_area->vm_start);
printk("vm_area_struct vm_end = %d\n", vm_area->vm_end);
printk("vm_area_struct vm_page_prot = %d\n", vm_area->vm_page_prot);
printk("vm_area_struct vm_flags = %d\n", vm_area->vm_flags);
if (s_pid == tsk->pid)
{
// 增加第二个进程的地址空间
vm_area->vm_end += 4096;
}
}
}
}
if (NULL != f_pte && NULL != s_pte)
{
// 将第一个进程字符串所在的页,添加到第二个进程中
set_pte(&s_pte[1], *f_pte);
}
return 0;
}