作者:刘洪涛,华清远见嵌入式学院讲师。
本文主要介绍一个linux内核线程的实例,以及在QEMU平台上测试的过程。
一、内核线程的创建
编写一个字符设备驱动,在驱动注册时,开启一个内核线程。在用户向设备写入数据时,字符设备的wirte方法能够激活此内核线程,并在线程中实现打印用户输入的数据。
驱动代码如下(在2.6.22内核上测试通过),关键部分加上了注释:
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h> /* printk(), min() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include <linux/kthread.h>
#include <linux/spinlock.h>
static int kthread_major = 0;
module_param(kthread_major, int, 0);
MODULE_AUTHOR("farsight");
MODULE_LICENSE("Dual BSD/GPL");
struct kthread_dev {
struct task_struct *thread;
struct cdev cdev;
char* name;
int data_size;
char data[100];
spinlock_t queue_lock;
};
int kthread_open(struct inode *inode,struct file *filp)
{
return 0;
}
ssize_t kthread_read(struct file *file, char __user *buff, size_t count, loff_t *offp)
{
return 0;
}
ssize_t kthread_write(struct file *file, const char __user *buff, size_t count, loff_t *offp)
{
int ret;
ret=sizeof(kthread_dev_obj->data);
if(count>(ret-1))
count=ret-1;
if(copy_from_user(kthread_dev_obj->data,buff,count)<0)//获取用户数据
{
goto out1;
}
spin_lock(&kthread_dev_obj->queue_lock);
kthread_dev_obj->data_size=count;
spin_unlock(&kthread_dev_obj->queue_lock);
wake_up_process(kthread_dev_obj->thread);//唤醒内核线程
return count;
out1:
return 0;
}
static int kthread_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
return 0;
}
static int kthread_release(struct inode *node, struct file *file)
{
return 0;
}
/*
* Set up the cdev structure for a device.
*/
static void kthread_setup_cdev(struct cdev *dev, int minor,struct file_operations *fops)
{
int err, devno = MKDEV(kthread_major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
err = cdev_add (dev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk (KERN_NOTICE "Error %d adding kthread%d", err, minor);
}
static struct file_operations kthread_remap_ops = {
.owner = THIS_MODULE,
.open = kthread_open,
.release = kthread_release,
.read = kthread_read,
.write = kthread_write,
.ioctl = kthread_ioctl,
};
static int kthread_fun(void * arg) //内核线程运行函数
{
while (!kthread_should_stop()) {
spin_lock(&kthread_dev_obj->queue_lock);
if(kthread_dev_obj->data_size){
spin_unlock(&kthread_dev_obj->queue_lock);
kthread_dev_obj->data[kthread_dev_obj->data_size]='\0';
printk(kthread_dev_obj->data);//打印出用户空间数据
printk("in kthread\n");
kthread_dev_obj->data_size=0;
}
else{
set_current_state(TASK_INTERRUPTIBLE);
spin_unlock(&kthread_dev_obj->queue_lock);
schedule();
}
}
return 0;
}
static int kthread_init(void)
{
int result;
dev_t dev = MKDEV(kthread_major, 0);
/* Figure out our device number. */
if (kthread_major)
result = register_chrdev_region(dev, 1, "kthread");
else {
result = alloc_chrdev_region(&dev, 0, 1, "kthread");
kthread_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "kthread: unable to get major %d\n", kthread_major);
return result;
}
if (kthread_major == 0)
kthread_major = result;
kthread_dev_obj= kmalloc(sizeof(struct kthread_dev), GFP_KERNEL);
kthread_setup_cdev(&kthread_dev_obj->cdev, 0,&kthread_remap_ops);
printk("kthread device installed, with major %d\n", kthread_major);
my_class= class_create(THIS_MODULE, "kthread");
// device_create(my_class, NULL, MKDEV(kthread_major, 0),NULL, "kthread");
device_create(my_class, NULL, MKDEV(kthread_major, 0), "kthread");//for
// 2.6.22
kthread_dev_obj->name="kthreadtest";//内核线程的名称
spin_lock_init(&kthread_dev_obj->queue_lock);
kthread_dev_obj->thread=kthread_run(kthread_fun,kthread_dev_obj,"%sd",kthread_dev_obj->name);//创建并运行内核线程
return 0;
}
static void kthread_cleanup(void)
{
kthread_stop(kthread_dev_obj->thread);//停止内核线程
cdev_del(&kthread_dev_obj->cdev);
unregister_chrdev_region(MKDEV(kthread_major, 0), 1);
device_destroy(my_class,MKDEV(kthread_major,0));
class_destroy(my_class);
kfree(kthread_dev_obj);
printk("kthread device uninstalled\n");
}
module_init(kthread_init);
module_exit(kthread_cleanup);
二、在QEMU平台上的测试方法
QEMU可以模拟很多硬件平台,使用QEMU适用于你手边没用硬件平台,或没用很好的内核调试工具的情况。
这里主要介绍使用QEMU模拟ARM开发环境,并运行linux系统的过程。
1、系统环境
操作系统平台:Ubuntu 10.10
交叉工具:arm-softfloat-linux-gnu
测试内核:Linux2.6.22
测试平台:RealView-EB (QEMU 模拟)
2、安装QEMU的方法
使用新立得获取安装包
安装完毕后,测试一下可以支持的ARM平台
$ qemu-system-arm -M ?
Supported machines are:
syborg Syborg (Symbian Virtual Platform)
musicpal Marvell 88w8618 / MusicPal (ARM926EJ-S)
mainstone Mainstone II (PXA27x)
n800 Nokia N800 tablet aka. RX-34 (OMAP2420)
n810 Nokia N810 tablet aka. RX-44 (OMAP2420)
cheetah Palm Tungsten|E aka. Cheetah PDA (OMAP310)
sx1 Siemens SX1 (OMAP310) V2
sx1-v1 Siemens SX1 (OMAP310) V1
tosa Tosa PDA (PXA255)
akita Akita PDA (PXA270)
spitz Spitz PDA (PXA270)
borzoi Borzoi PDA (PXA270)
terrier Terrier PDA (PXA270)
connex Gumstix Connex (PXA255)
verdex Gumstix Verdex (PXA270)
lm3s811evb Stellaris LM3S811EVB
lm3s6965evb Stellaris LM3S6965EVB
realview-eb ARM RealView Emulation Baseboard (ARM926EJ-S)
realview-eb-mpcore ARM RealView Emulation Baseboard (ARM11MPCore)
realview-pb-a8 ARM RealView Platform Baseboard for Cortex-A8
realview-pbx-a9 ARM RealView Platform Baseboard Explore for Cortex-A9
versatilepb ARM Versatile/PB (ARM926EJ-S)
versatileab ARM Versatile/AB (ARM926EJ-S)
integratorcp ARM Integrator/CP (ARM926EJ-S) (default)
3、制作内核镜像
(1)配置好交叉开发工具
(2)配置内核
#cp arch/arm/configs/realview_defconfig .config
#make menuconfig
配置支持initial ramdisk
配置支持Ramdisk块设备:
Device Drivers ->Block devices->RAM disk support
其中:Default RAM disk size (kbytes)必须要改成和你的RAMDISK镜像一样的大小。
配置内核添加调试选项:
Kernel hacking ->Compile the kernel with debug info
设置内核支持ext2文件系统
保存退出。
(3) 将上述的内核线程驱动加入到内核中,然后编译内核。
#make zImage
4、制作根文件系统
制作一个8M的ramdisk根文件系统。这个步骤没有什么特别的,可以参考其它资料。
5、安装gdb
(1)下载GDB源码:
http://ftp.gnu.org/gnu/gdb/gdb-7.2.tar.bz2
(2)交叉编译GDB
#./configure --target=arm-softfloat-linux-gnu –prefix=/home/lht/QEMU/arm-gdb
#make && make install
6、调试内核
#qemu-system-arm -M realview-eb -kernel ./zImage -initrd ./initrd.img -nographic -append "console=ttyAMA0" -m 64 -s -S
系统会暂停,等待远端gdb连接调试。在另一终端下运行:
#ddd -debugger /home/lht/QEMU/arm-gdb/bin/arm-softfloat-linux-gnu-gdb ~/disk2/s3c2410/linux-2.6.22.6-qemu/vmlinux
此时会出现ddd运行界面,然后运行远程连接命令:
(gdb)target remote localhost:1234
此时就可以运行gdb命令调试内核了。
如:在kthread_fun函数中设置一个断点的方法
连接后,搜索栏中输入kthead_fun,出现如下图的显示:
在view菜单中打开汇编窗口,然后在汇编窗口中设置断点(比在c中准确)。
Gdb命令行输入c
(gdb) c
启动目标系统
系统会在断点处停止,接下就可以用ddd提供图形调试工具调试代码了。
系统正常启动后,测试结果如下:
[root@farsight /]# echo 123456 > /dev/kthread
123456
in kthread
[root@farsight /]# ps w
PID USER VSZ STAT COMMAND
1 0 0 SW [swapper]
2 0 0 SW< [kthreadd]
3 0 0 SWN [ksoftirqd/0]
4 0 0 SW< [watchdog/0]
5 0 0 SW< [events/0]
6 0 0 SW< [khelper]
40 0 0 SW< [kblockd/0]
41 0 0 SW< [kseriod]
53 0 0 SW [pdflush]
54 0 0 SW [pdflush]
55 0 0 SW< [kswapd0]
56 0 0 SW< [aio/0]
131 0 0 SW< [kthreadtestd]
188 0 0 SW< [mtdblockd]
196 0 0 SW< [kpsmoused]
202 0 1996 S init
206 0 1492 S < /bin/udevd --daemon
208 0 2000 S -/bin/sh
209 0 2000 R ps w
文章评论(0条评论)
登录后参与讨论