博文

目前显示的是 八月, 2018的博文

初试Netlink之unicast

        在之前已经了解了好几种内核态与用户态之间数据的交换方法,但那些方法都是单向的(即单工),其关系是一对一的,而Netlink Socket则是一种用于内核与用户空间之间传递信息的特殊IPC,在用户进程中,其以标准的Socket API为内核和用户之间提供了全双工的通信通道,在内核模块中则提供了一类特殊的API。相比TCP/IP socket所使用的AF_INET地址族,Netlink socket则使用AF_NETLINK地址族,每一个Netlink Socket都在Kernel的include/uapi/linux/netlink.h(linux-3.11.0-rc4,关于Netlink部分相关文档,如无注明均以该版本为基准)中定义了相应的protocol type。         在Linux  Kernel中,以模块形式去创建Netlink Socket时需要涉及到如下相关的API及数据结构:         1.创建Socket            在include/linux/netlink.h文件中,有如下的定义:            /* optional Netlink kernel configuration parameters */            struct netlink_kernel_cfg {                    unsigned int    groups;                    unsigned int    flags;                    void            (*input)(struct sk_buff *skb);                    struct mutex    *cb_mutex;                    void            (*bind)(int group);                    bool            (*compare)(struct net *net, struct sock *sk);            };            extern struct sock *__netlink_kernel_create(s

C编程使用内联汇编控制PC蜂鸣器发声

有了“内联汇编控制PC蜂鸣器”的基础了解后,我们使用内联汇编来改造“C编程控制PC蜂鸣器”一文的例子,最终的代码如下: #include <stdio.h> #include <unistd.h> #include <sys/io.h> /* The clock frequency of the i8253/i8254 PIT */ #define PIT_TICK_RATE 1193182ul void beep(unsigned int value) { unsigned int count = 0; unsigned char val; unsigned short port; if (value > 20 && value < 32767) count = PIT_TICK_RATE / value; iopl(3); if (count) { val = 0xB6; port = 0x43; asm volatile("outb %0,%1" : : "a" (val), "dN" (port)); val = count & 0xff; port = 0x42; asm volatile("outb %0,%1" : : "a" (val), "dN" (port)); val = (count >> 8) & 0xff; asm volatile("outb %0,%1" : : "a" (val), "dN" (port)); port = 0x61;

使用EXPORT_SYMBOL

在Linux内核里,我们编写驱动程序时,如果遇到的驱动太大,需要在各个模块之间共享一些变量或函数等,此时就需要用到内核里的EXPORT_SYMBOL宏了,其在include/linux/export.h文件中定义,而最常用的有如下两个: #define EXPORT_SYMBOL(sym)                                      \         __EXPORT_SYMBOL(sym, "") #define EXPORT_SYMBOL_GPL(sym)                                  \         __EXPORT_SYMBOL(sym, "_gpl")        其中,多加了_GPL尾的量需要在使用MODULE_LICENSE()宏定义为GPL协议的模块里才能引用,不然加载驱动时会有类似Unknown symbol export_orig_gpl (err 0)的提示(该提示取自下面的例子),当然,模块之间也需要有加载先后顺序,其中EXPORT的模块需先加载,extern的后加载,不然也会有类似如上的提示。        这里的例子会用到两个源码文件,作为export的文件名为export_orig.c,作为extern的文件名为export_symbol_example.c,相应的源码如下:        export_orig.c: #include <linux/kernel.h> #include <linux/module.h> static int export_orig_normal; static int export_orig_gpl; EXPORT_SYMBOL(export_orig_normal); EXPORT_SYMBOL_GPL(export_orig_gpl); static __init int export_orig_init(void) {         printk("Enter %s\n", __func__);         export_orig_normal = 10;         expo

制造、调试OOPS

1.前言     在Linux系统里出现kernel panic时会打印OOPS信息,把当前的寄存器状态、堆栈信息、完整的Call trace都打印出来,以便我们更好的定位错误。下面我们将会自已制造一个OOPS,并采用多种方式来定位调试OOPS,以便大家更好的了解内核,深入探索内核,利用内核异常信息来定位处理问题。 2.制造OOPS     这次我们以模块的形式来制造一个OOPS,文件名为oops_example.c,内容如下:     #include <linux/kernel.h>     #include <linux/module.h>     static __init int oops_example_init(void)     {             int *p = 0;             printk("Hi OOPS!\n");             *p = 12;             return 0;     }     static __exit void oops_example_exit(void)     {             printk("Bye OOPS!\n");     }     module_init(oops_example_init);     module_exit(oops_example_exit);     相应的Makefile文件内容如下:     obj-m += oops_example.o     CUR_PATH:=$(shell pwd)     LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6     all:             make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules     clean:             make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean     相应的源码文件目录树如下:     oops_example/{Makefile,oops_example.c}

内联汇编控制PC蜂鸣器

本文,我们对控制PC蜂鸣器的操作改为使用内联汇编的方法,里面会使用到约束“a”和“dN”,其中前者对应eax,后者对应edx,并且N表示0-255之间的数,有了这些了解后,我们改造下“I/O映射之I/O端口”里的例子,改造后的代码如下: #include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/timex.h> static int value = 0; module_param(value, int, 0644); MODULE_PARM_DESC(value, "the beep frequences(20,32767)"); static __init int ioport_beep_init(void) { unsigned int count = 0; u8 val; u16 port; if (value > 20 && value < 32767) count = PIT_TICK_RATE / value; if (count) { /* set command for counter 2, write 2 bytes */ val = 0xB6; port = 0x43; asm volatile("outb %0,%1" : : "a" (val), "dN" (port)); /* select desired HZ */ val = count & 0xff; port = 0x42; asm volatile("outb %0,%1" : : &qu

PC蜂鸣器音乐

有了“使用procfs”、“I/O映射之I/O端口”、“内核读写磁盘文件”这三篇文章的基础后,我们将其结合,实现如下功能的实例: 1.打开传入的音乐谱文件(通过procfs接口); 2.读取音乐谱文件(以“频率”、“延时”、“频率”、“延时”、……这样的格式保存); 3.解析读取的文件; 4.将解析的数据传送去操作8254,让PC蜂鸣器弹奏文件对应的音乐。 好了,基础的内容看开头提及的文章,接下来上代码: #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <linux/timex.h> #ifndef MAX_PATH #define MAX_PATH 256 #endif #define PACKAGE_SIZE 512 extern void msleep(unsigned int msecs); struct proc_dir_entry * slam_dir = NULL; struct proc_dir_entry * slam_entry = NULL; static char * dir_name = "slam_song"; static char * entry_name = "path"; static void beep(unsigned int freq, unsigned int delay) { unsigned int count; if (freq) count = PIT_TICK_RATE / freq; else count = 20; outb_p(0xB6, 0x43); outb_p(count & 0xff, 0x42); outb((count >> 8) & 0xff, 0x42);

使用firmware

我们在使用Ralink芯片的WiFi时,很多型号是需要在加载模块时也加载固件的,这样做是有好处的,一可以省成本(少了保存固件的存储芯片和生产烧录),二是解决硬件BUG(通过更新固件来解决批量硬件存在的严重BUG),三是功能升级。总之好处多多,怎么都可以挖掘到。 在Linux中关于固件常用到如下函数,其在include/linux/firmware.h声明,在drivers/base/firmware_class.c实现,相应的函数原型如下: static inline int request_firmware(const struct firmware **fw, const char *name, struct device *device) ; static inline void release_firmware(const struct firmware *fw) ; 其中firmware结构体定义如下: struct firmware { size_t size; const u8 *data; struct page **pages; /* firmware loader private fields */ void *priv; }; 而相应的device结构体在include/linux/device.h中定义,我们等会只是构建一个临时的device,该部分在后期的内核模型中再说明,其使用device_initialize函数来初始化,在drivers/base/core.c中定义。 接下来上代码吧: #include <linux/module.h> #include <linux/firmware.h> #include <linux/device.h> #define SLAM_TEST_FIRMWARE "rt73.bin" static const struct firmware * fwp; static struct device slam_device = { .init_name = "slam0", }; static void pri

Shell脚本弹奏中文版“生日快乐歌”

在《Shell命令控制蜂鸣器发声》一文中,我们了解到了如何在Ubuntu下安装beep命令来控制PC主板上蜂鸣器发声,这次我们想让蜂鸣器弹奏出中文版的“生日快乐歌”,首先还是要用sudo modprobe pcspkr命令加载驱动,然后在Shell脚本所在目录下执行bash -e beep_birthday_song.sh命令,这样就可以执行我们写的脚本弹奏了,我们的脚本名字为beep_birthday_song.sh,内容如下: #!/bin/sh freq=(392 392 440 392 523 494 392 392 440 392 587 523 392 392 784 659 523 494 440 698 698 659 523 587 523) delay=(375 125 500 500 500 1000 375 125 500 500 500 1000 375 125 500 500 500 500 1000 375 125 500 500 500 1000) i=0 len=${#freq[*]} while [ $i -lt $len ] do beep -f ${freq[$i]} -l ${delay[$i]} let ++i done 参考网址: http://jpuyy.com/2012/11/linux-beep.html http://stackoverflow.com/questions/12919378/solvedplaying-beep-in-c-linux http://kirrus.co.uk/2010/09/linux-beep-music/ http://snipplr.com/view/62662/jingle-bells-song/ http://wiki.mikrotik.com/wiki/Super_Mario_Theme http://frank-buss.de/beep/index.html http://stackoverflow.com/questions/7247279/bash-set-e-and-i-0let-i-do-not-agree http://zhenshi4597.blog.51cto.com/1891465/871166 http:/

内核读写磁盘文件

在用户态,即我们一般编写的C语言程序中,可以使用open、close、read、write等系统调用对磁盘文件进行操作,那么在内核态呢?同样有相应的函数可以使用,而这些函数却是open这些系统调用会使用到的,故而我们还是在内核面向应用的层级进入,实现对磁盘文件的操作。关于如何从open一层层往下找到我们即将要使用的函数过程就不说了,下面直接给出我们用到的函数定义或原型信息,都在include/linux/fs.h文件中有声明,分别在fs/open.c和fs/read_write.c实现下面两类操作函数: 1.打开、关闭文件 struct file *filp_open(const char *filename, int flags, umode_t mode) ; 其中,filename就是文件名(包含完整路径),flags必须包含O_RDONLY、O_WRONLY或O_RDWR其中之一,而mode只有在flags包含O_CREAT时才有作用,表示打开的文件不存在就进行创建时该文件的访问权限,可以是S_IRWXU、S_IRUSR或S_IWUSR等的组合,对于O_和S_开头的这些宏说明可使用man 2 open命令查看,而这些宏则分别在内核include/uapi/asm-generic/fcntl.h和include/uapi/linux/stat.h文件中定义。 int filp_close(struct file *filp, fl_owner_t id) ; 其中,filp指针就是filp_open的返回值,id是POSIX线程的ID,而我们在内核态中操作,暂不需要用到,故一般设置为NULL即可。 2.读写文件 ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos); ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) ; 其中,file指针使用filp_open的返回值,buf是用户空间的内存(读写数据存放位置),count是读写多少单位(字节)的数据,pos表示当前文件读写所处于的偏移位置。 相信大家都注意到