makefile学习

 /* hello.c */

        #include <linux/module.h>       /* Needed by all modules */

        #include <linux/config.h>       /* Needed for KERN_ALERT */

        #include <linux/init.h>         /* Needed for the module-macros */

        static int __init hello_init(void)    // Module entry function specified by module_init()

        {

                printk(KERN_ALERT "Hello,world!\n");

                return 0;

        }

        static void __exit hello_exit(void)  //Module exit function specified by module_exit()

        {

                printk(KERN_ALERT "Goodbye,cruel world!\n");

        }

        module_init(hello_init);

        module_exit(hello_exit);

        MODULE_LICENSE("Dual BSD/GPL");  //should always exist or you’ll get a warning

        MODULE_AUTHOR("BENSON"); //optional

        MODULE_DESCRIPTION("STUDY_MODULE"); //optional

        /* Makefile */

        # Makefile 2.6

        obj-m += hello.o

        KDIR:=/lib/modules/$(shell uname -r)/build

        #PWD=$(shell pwd)

        all:

                make -C $(KDIR) M=$(PWD) modules

        clean:

                make -C $(KDIR) M=$(PWD) clean

 

        obj-m := hello.o表示编译后生成hello.o模块。
        $(KDIR) 指定了内核源码的路径,“M=”表示这是个外部模块,M=$(PWD) 指定了该模块文件所在的路径。

        注: makefile预定义了$(PWD)变量,此处可以不必重复定义。

        执行#make编译成功后

        加载模块

        #insmod hello.ko

        #lsmod 输出内核已加载模块信息,可以查看到刚刚加载成功的hello模块

        ……

        Module                  Size  Used by

        hello                   5632  0

        可以在日志里查看加载模块时的信息

        #vi /var/log/messages

        ……

        Sep 27 13:25:21 localhost kernel: Hello,world!

        卸载模块

        #rmmod hello.ko

        #lsmod 发现hello模块已经被卸载

        查看日志信息

        #vi /var/log/messages……

        …………

        Sep 27 13:26:58 localhost kernel: Goodbye,cruel world!


 
一个学习Linux设备驱动程序都会碰到的第一个例程:
  
#include linux/init.h>
#include linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
    printk(KERN_ALERT "Hello, Tekkaman Ninja !\n");
    return 0;
}
static [color=#ff0000]void[/color] hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);

1、Makefile书写:
   KERNELDIR = /usr/src/linux_2.6.26/linux-2.6.26 #内核原代码树位置
   PWD = $(shell pwd)
   INSTALLDIR = /lib/modules/2.6.26linux2.6.26

   #1   obj-m := hello.o   模块只有一个文件
   #2   obj-m := hello.o
        hello-objs := one.o two.o three.o  模块有多个文件
   #3  如果模块是由一个目录(online)组成的写法如下
        a、在这个目录的上一级目录中的makefile中加入
             [color=#000000]obj-m += online/  #告诉在编译的时候找到online目录[/color]
[color=#000000]             obj-$(CONFIG_USB_GADGET_ONLINE) += online/  这种方式是用配置选项(kconfig文件中添加配置选项)来判断是否编译这个子目录(即模块)[/color]
[color=#000000]        b、最后,我们在online/下创建新文件Makefile,并且添加下面一行到其内。
               obj-m += netmeeting.o[/color]
[color=#000000]               netmeeting-objs := one.o two.o three.o 如果有多个文件[/color]
    modules:
       [TAB] $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    modules_install:
       [TAB] cp hello.ko $(INSTALLDIR)     
    clean:
       [TAB] rm -rf *.o *.ko *.mod.c *.o.cmd .tmp_versions *.mod.o .cmd
    .PHONY :modules modules_install clean


   2、编译运行
      insmod
      rmmod
      lsmod
   3、调试查看输出信息
      a、在模块中添加调试输出语句 printk 
           cat /proc/kmsg   (rsyslog没有运行)
           dmesg (syslogd进程已经运行  /etc/rc.d/init/rsyslog 开启)
           cat /var/log/message
      注意:
          [color=#000000]printk[/color]([color=#000000]KERN_ALERT[/color]"xxxx“);
宏的定义等级如下(printk.c):        
#define KERN_EMERG "" /* system is unusable   */
#define KERN_ALERT "" /* action must be taken immediately */
#define KERN_CRIT "" /* critical conditions   */
#define KERN_ERR "" /* error conditions   */
#define KERN_WARNING "" /* warning conditions   */
#define KERN_NOTICE "" /* normal but significant condition */
#define KERN_INFO "" /* informational   */
#define KERN_DEBUG "" /* debug-level messages   */

   当指定的等级小于console_level(默认4)时候,就可以直接在tty上面打印输出语句
console_level设置方式:
    通过对/proc/sys/kernel/printk的访问来改变[color=#000102]console_loglevel的值:[/color]
[Tekkaman2440@SBC2440V4]#echo 1 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk
1       4       1       7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
[Tekkaman2440@SBC2440V4]#rmmod hello
[Tekkaman2440@SBC2440V4]#echo 7 > /proc/sys/kernel/printk
[Tekkaman2440@SBC2440V4]#cat /proc/sys/kernel/printk7       4       1       7
[Tekkaman2440@SBC2440V4]#insmod hello.ko
Hello, Tekkaman Ninja !
[Tekkaman2440@SBC2440V4]#rmmod hello
Goodbye, Tekkaman Ninja !
Love Linux !Love ARM ! Love KeKe !
四个数字的含义:当前的loglevel、默认loglevel、最小允许的loglevel、引导时的默认loglevel。
 
   4、模块中使用的头文件
     
#include linux/init.h>
#include linux/module.h>

  5、模块中使用的描述性定义
   MODULE_AUTHOR("");
   MODULE_DESCRIPTION("");
   MODULE_VERSION("");
   MODULE_ALIAS("");
   MODULE_DEVICE_TABLE("");
    描绘性定义一般放在最后面

   6、模块中的参数
    (内核允许对驱动程序指定参数,而这些参数可在装载驱动程序模块时改变[color=#000000]。 example : insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1[/color])
     [color=#000000]对于如何向模块传递参数,Linux kernel 提供了一个简单的框架。其允许驱动程序声明参数,并且用户在系统启动或模块装载时为参数指定相应值,在驱动程序里,参数的用法如同全局变量。这些模块参数也能够在sysfs中显示出来。结果,有许许多多的方法用来创建和管理模块参数。
    通过宏module_param()定义一个模块参数:
module_param(name, type, perm);
    这里,name既是用户看到的参数名,又是模块内接受参数的变量; type表示参数的数据类型,是下列之一:byte, short, ushort, int, uint, long, ulong, charp, bool, invbool。这些类型分别是:a byte, a short integer, an unsigned short integer, an integer, an unsigned integer, a long integer, an unsigned long integer, a pointer to a char, a Boolean, a Boolean whose value is inverted from what the user specifies. The byte type is stored in a single char and the Boolean types are stored in variables of type int. The rest are stored in the corresponding primitive C types. 最后,perm指定了在sysfs中相应文件的访问权限。访问权限用通常的八进制格式来表示,例如,用0644(表示ower具有读写权限,group和 everyone只读权限), 或者用通常的S_Ifoo定义,例如,S_IRUGO | S_IWUSR (表示everyone具有读权限,用户具有写权限)。用0表示完全关闭在sysfs中相对应的项。
    其实宏不会声明变量,因此在使用宏之前,必须声明变量。所以,典型地用法如下:
static unsigned int use_acm = 0;  
module_param(use_acm, uint, S_IRUGO);
    这些必须写在模块源文件的开头部分。即use_acm是全局的。
    我们也可以使模块源文件内部的变量名与外部的参数名有不同的名字。这通过宏module_param_named()定义。
module_param_named(name, variable, type, perm);
    这里name是外部可见的参数名,variable是源文件内部的全局变量名。例如:
static unsigned int max_test = 9;
module_param_name(maximum_line_test, max_test, int, 0);
    如果模块参数是一个字符串时,通常使用charp类型定义这个模块参数。内核复制用户提供的字符串到内存,并且相对应的变量指向这个字符串。例如:
static char *name;
module_param(name, charp, 0);
    另一种方法是通过宏module_param_string()让内核把字符串直接复制到程序中的字符数组内。
module_param_string(name, string, len, perm);
    这里,name是外部的参数名,string是内部的变量名,len是以string命名的buffer大小(可以小于buffer的大小,但是没有意义),perm表示sysfs的访问权限(或者perm是零,表示完全关闭相对应的sysfs项)。例如:
static char species[BUF_LEN];
module_param_string(specifies, species, BUF_LEN, 0);
    上面说得只是给模块传入一个参数的情况,如果给模块传入多个参数,那该怎么办呢?可以通过宏module_param_array()给模块传入多个参数。 用法如下:
module_param_array(name, type, nump, perm);
    这里,name既是外部模块的参数名又是程序内部的变量名,type是数据类型,perm是sysfs的访问权限。指针nump指向一个整数,其值表示有多少个参数存放在数组name中。值得注意是name数组必须静态分配。例如:
static int finsh[MAX_FISH];
static int nr_fish;
module_param_array(fish, int, &nr_fish, 0444);
    通过宏module_param_array_named()使得内部的数组名与外部的参数名有不同的名字。例如:
module_param_array_named(name, array, type, nump, perm);
    这里的参数意义与其它宏一样。
    最后,通过宏MODULE_PARM_DESC()对参数进行说明:
static unsigned short size = 1;
module_param(size, ushort, 0644);
MODULE_PARM_DESC(size, “The size in inches of the fishing pole” \
“connected to this computer.” );
    使用这些宏时需要包含头文件。[/color]

[color=#ff0000]注意:[/color]
   由于模块参数指的是外部向模块传递参数,模块声明的变量能否被内核其他代码和模块使用呢?(是不是只要是非静态的且导出符号,内核和其他模块就能共享这个变量呢?)

  example code :
  
#include linux/init.h>
#include linux/module.h>
#include linux/moduleparam.h>
MODULE_LICENSE("Dual BSD/GPL");
static char *whom = "Tekkaman Ninja";
static int howmany = 1;
static int TNparam[] = {1,2,3,4};
static int TNparam_nr = 4;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
module_param_array(TNparam , int , &TNparam_nr , S_IRUGO);
static int hello_init(void)
{
    int i;
    for (i = 0; i  howmany; i++)
        printk(KERN_ALERT "(%d) Hello, %s !\n", i, whom);
    for (i = 0; i ; i++)
        printk(KERN_ALERT "TNparam[%d] : %d \n", i, TNparam[i]);
    return 0;
}
static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, Tekkaman Ninja !\n Love Linux !Love ARM ! Love KeKe !\n");
}
module_init(hello_init);
module_exit(hello_exit);

编译完成后
测试1
   insmod hello-param.ko howmany=2 whom="KeKe" TNparam=4,3,2,1
测试2
  [Tekkaman2440@SBC2440V4]#insmod hello-param.ko howmany=2 whom="KeKe"  TNparam=4,3,2,1,5,6,7,8
TNparam: can only take 4 arguments
hello_param: `4' invalid for parameter `TNparam'
insmod: cannot insert 'hello-param.ko': Invalid parameters (-1): Invalid argument
注意:
   这个测试说明module_param_array(TNparam , int , &TNparam_nr , S_IRUGO),参数[color=#ff0102]TNparam_nr不能自己限制参数的个数,而需要程序员自己确定参数的个数,否则数组 [/color][color=#0000cc]TNparam可能会越界。(个人认为如果传送数组形式的参数最好再定义一个宏来确定数组的大小) [/color]

7、导出符号
  [color=#000000]  当装载模块的时候,模块动态地链接入内核之中。动态链接的二进制代码只能调用外部函数,然而,外部函数必须明确地输出,在内核中,通过EXPORT_SYMBOL()和EXPORT_SYMBOL_GPL来达到这个目的。
输出的函数可以被其它模块调用。没有输出过的函数不能被其它模块调用。模块比核心内核映像代码具有更严格的链接和调用规则。因为所有核心源文件链接成一个单一的作为基础的映像,因此在内核中核心代码可以调用任何非静态的接口。当然,输出符号也必须是非静态属性。
一套输出的内核符号称之为输出的内核接口,甚至称之为kernel API。
输出一个内核符号是举手之劳之事。当函数声明之时,在其后用EXPORT_SYMBOL()把函数输出。
例如:
/* it will receive control requests including set_configuration(), which enables non-control requests.
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
{

}
EXPORT_SYMBOL(usb_gadget_register_driver) ;
    从此以后,任何模块都可以调用函数usb_gadget_register_driver(),只要在源文件中包含声明这个函数的头文件,或者extern这个函数的声明。
    有些开发者希望他们的接口只让遵从GPL的模块调用。通过MODULE_LICENSE()的使用,内核链接器能够强制保证做到这点。如果你希望前面的函数仅被标有GPL许可证的模块访问,那么你可以用如下方式输出符号:
EXPORT_SYMBOL_GPL(usb_gadget_register_driver);
    如果你的代码配置为模块方式,那么必须确保:源文件中使用的所有接口必须是已经输出的符号,否则导致在装载时链接错误。[/color]

原文地址:https://www.cnblogs.com/276815076/p/2052052.html