资源描述
Linux内核作业
一、 程序设计思路
题目中说明要使用系统调用,所以首先要先下载编译Linux内核,并在内核中加入相关的系统调用声明,序号等等的。
进程是通过list_head双向链表连接起来的,所以根据一个进程就可以得到其他的进程描述符。要获得第一个进程,我们选择了init进程。要把进程以树的形式输出,所以要用到深度优先遍历。在进程描述符中,通过children可以获得这个进程的子进程。根据优先遍历的方法,进行递归的打印输出,就可以生成一棵进程树。打印过程中,缩进采用层级的打印空格实现。
二、 程序内容
程序中首先要在一些表中进行声明。
主要的程序在sys.c中实现。主要有两个函数构成,一个系统调用函数,一个用于递归调用的函数。
在系统调用函数中,会调用这个递归调用函数。
三、 问题及解决办法
1. 不了解进程描述符
struct task_struct {
//这个是进程的运行时状态,-1代表不可运行,0代表可运行,>0代表已停止。
volatile long state;
/*
flags是进程当前的状态标志,具体的如:
0x00000002表示进程正在被创建;
0x00000004表示进程正准备退出;
0x00000040 表示此进程被fork出,但是并没有执行exec;
0x00000400表示此进程由于其他进程发送相关信号而被杀死 。
*/
unsigned int flags;
//表示此进程的运行优先级
unsigned int rt_priority;
struct list_head tasks;
//该结构体记录了进程内存使用的相关情况
struct mm_struct *mm;
/*进程的一些状态参数*/
int exit_state;
int exit_code, exit_signal;
//进程号
pid_t pid;
//进程组号
pid_t tgid;
//real_parent是该进程的“亲生父亲”,不管其是否被“寄养”。
struct task_struct *real_parent;
//parent是该进程现在的父进程,有可能是“继父”
struct task_struct *parent;
//这里children指的是该进程孩子的链表,可以得到所有孩子的进程描述符
struct list_head children;
//sibling该进程兄弟的链表,也就是其父亲的所有孩子的链表
struct list_head sibling;
//这个是主线程的进程描述符
struct task_struct *group_leader;
//这个是该进程所有线程的链表。
struct list_head thread_group;
//该进程使用cpu时间的信息,utime是在用户态下执行的时间,stime是在内核态下执行的时间
cputime_t utime, stime;
//下面的是启动的时间,只是时间基准不一样
struct timespec start_time;
struct timespec real_start_time;
//comm是保存该进程名字的字符数组,长度最长为15,因为TASK_COMM_LEN为16
char comm[TASK_COMM_LEN];
/* 文件系统信息计数*/
int link_count, total_link_count;
/*该进程在特定CPU下的状态*/
struct thread_struct thread;
/* 文件系统相关信息结构体*/
struct fs_struct *fs;
/* 打开的文件相关信息结构体*/
struct files_struct *files;
/* 信号相关信息的句柄*/
struct signal_struct *signal;
struct signal_struct *signal;
/*这些是松弛时间值,用来规定select()和poll()的超时时间,单位是纳秒nanoseconds */
unsigned long timer_slack_ns;
unsigned long default_timer_slack_ns;
};
2. 遍历进程的方法
#define list_entry(ptr,type,member)\
container_of(ptr,type,member)
#define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr,type,member) ( {\
const typeof( ((type*)0)->member ) *__mptr=(ptr);\
(type*)( (char*)__mptr - offsetof(type,member) );} )
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)
通过这些宏,知道了如何去使用这些宏进行遍历。
四、 代码及说明
1. arch/x86/kernel/syscall_table_32.S
.long sys_yyycall
2. arch/x86/include/asm/unistd_32.h
#define __NR_yyycall 337
#ifdef __KERNEL__
#define __NR_syscalls 338
3. include/linux/syscalls.h
asmlinkage long sys_yyycall(void);
4. kernel/sys.c
//task表示当前的进程描述符,n表示多少层
static void print_child(struct task_struct* task,int n)
{
//链表指针
struct list_head *pos;
//进程描述符的指针
struct task_struct* p;
//临时变量i,用于输出空格
int i;
for(i=n;i>0;i--)
printk(" ");
//输出进程树
printk("|————%d\n",task->pid);
//遍历子进程
list_for_each(pos,&task->children)
{
//根据list_head获取进程
p=list_entry(pos, struct task_struct,sibling);
if(p!=NULL)
{
n++;
//递归调用
print_child(p,n);
n--;
}
}
}
asmlinkage long sys_yyycall(void)
{
printk("********************************************\n\n");
//以init进程为初始进程,运行递归调用程序
print_child(&init_task,0);
printk("********************************************\n\n");
return 1;
}
5. 用户程序linux.c
#include<stdio.h>
int main()
{
//调用了337系统调用,并获得返回值
int a = syscall(337);
printf(“%d”,a);
return 0;
}
五、 运行结果及说明
首先对内核进行编译,重启。
然后编译用户程序,并运行,输出返回值为1。
可以用dmesg查看系统日志,可以观察到结果是以树的形式展示。
六、 运行截图
运行结果如图所示。
展开阅读全文