1.insmod 是系统调用 kernel/module.c,The function sys_init_module allocates kernel memory(how)=>( The memory is allocated with vmalloc function),系统调用函数把模块复制到内存区,通过内核符号表解决内核引用的问题,并且呼叫模块初始化函数使一切准备就绪。
2.在内核源代码中,使用前缀sys_ 就是系统调用。
3.modprobe 命令比insmod命令高级一点,它会自动解决模块引用不成功的问题。
4.如果模块正在被使用,清除模块的任务将会失败,或者这个模块已经被定义为不可清除
5.lsmod 将会列出目前正在内核被加载的模块还有例如其他模块正在使用一个特殊的模块,他是通过读 /proc/modules虚拟文件实现的。当前加载的模块也可以在虚拟系统文件 /sys/module读取。
6.在装载模块时,在链接的过程中会寻找一个目标文件 vermagic.o(不过,在debian中我没有找到这个文件)和当前的系统配置相对照,还有 vmlinux是做什么用的?
8.当你写的驱动需要在不同版本的linux上运行时,你这时就需要充分利用macros和#ifdef结构体了,类似大量的定义在这个文件linux/veersion.h中,这个头文件自动包括linux/module.h
UTS_RELEASE 这个宏定义了一个描述这个代码树的版本的字符串,例如”2.6.10”
LINUX_VERSION_CODE 定义了一个代表内核版本的二进制文件,一个字节代表版本的一部分,如,2.6.10是132618(0x020610)
KERNEL_VERSION(major,minor,release) KERNEL_VERSION(2.6.10)==>132618,
not clutter driver (填充)
9.解决 platform dependency
The best way is to release your driver under a GPL-compatible license and contribute it to the mainline kernel,distributing your driver in source form and a set of scripts to compile it on the user’s platform
msdos relies on symbols exported by the fat module, and each input USB device stacks on the usbcore and input modules. 有意识的去使用modprobe,这一句命令就可以代替很多insmod命令, 如果你写的模块希望被其他模块所使用,最好加入以下两句话 EXPORT_SYMBOL(name); EXPORT_SYMBOL_GPL(name); 具体的请参看<linux/module.h>吧,在模块中有一个“ELF”部分,在那里导入了有关符号表的东西。 MODULE_AUTHOR(); MODULE_VERSION(); MODULE_ALIAS(); MODULE_DEVICE_TABLE();
module__init 是强制性, 这个宏增加了一个的段对于模块的目标代码在初始化函数,没有这个定义,初始化函数是从来不会调用的。
模块可以注册不同的设备,对于不同的设备,这有一个特别的函数完成注册,传递给内核注册函数的参数一般是指向新设备的数据结构和这个注册设备的名字,这个数据结构经常包含指向模块函数的指针,这也是在模块体内部的函数得到调用的方法。
以下这些东西:串口,miscellaneous(杂项)设备,系统文件,/proc 文件,可执行的域名,line discipline(线性规划),这些文件不是直接与硬件打交道,但保持这软件抽象域,这些东西是可以被注册的。
这还有其他设备的插件也可以被注册,绝大多数用register_前缀。
11.清除函数 a function marked __exit can be called only at module unload ro system shutdown time.any other use is an error. and it (__exit)is vital for cleanup function.
In the linux kernel, err codes are negative numbers defined in the <linux/errno.h>
p52–p53的代码没有看明白。
1.你必须准备好只要你的代码完成它的第一步注册就有可能被调用,把你模块需要的设备准备好以后再去注册你的初始化函数。
2.避免把初始化函数失败。
###.模块参数
IDE can allow user control of DMA operations.
hardware need to know I/O ports and I/O memory address.
参数值可以被赋值在使用命令 insmod 或者 modprobe的时候,使用module_param macro,which is defined in
static char *whom = "world"; static int howmany = 1; module_param(howmany, int, S_IRUGO); module_param(whom, charp, S_IRUGO);
使用命令 insmod hellop howmany=10 who=”Mom”;(有问题)
数组参数使用module_param_array(name,type,num,perm); the perm was defined in <linux/stat.h>,
第一个字符设备驱动
在dev_t中,12位的主设备号,20位的次设备号,你的代码应该充分利用在 <linux/kdev_t>的宏,为了去获得主设备号和次设备号dev_t,使用如下命令:
MAJOR(dev_t dev); MINOR(dev_t dev);
then,use MKDEV(int major, int minor);
为一个字符串设备获得设备号的函数是
register_chrdev_region(dev_t first,unsigned int count, char *name)
first 是你申请的开始设备号的范围,这经常从0开始,count是你申请的设备的总数 name必须和你的number相对应,而且这个名字就在/proc/devices中显示,另外,这个/proc/目录是个虚拟目录,它时刻在读取计算机的各个硬件cpu、io、模块等信息,要注意利用啊。 这个函数如果正确执行,会返回一个0,否则就返回一个负数,现在很多人努力使用这个函数,
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
记住,申请完设备后一定要记得释放,驱动程序应该始终使用alloc_chrdev_region.
void unsigned_chrdev_region(dev_t first,unsigned int count)
Major devices numbers 静态分配的多些,这些设备清单在内核的Documentayion/devices.txt,如果你的设备不工作,那么试试以下两种方法: 1.使用未使用的静态主设备号;2.使用动态分配的方法,in other word,我们应该更多的使用alloc_chrdev_region而不是register_chrdev_region.
是关于驱动程序的,例如 /dev/null,/dev/zero 的主设备号是1,虚拟控制台和串口终端是4,VCSL和VCSL设备是7.
由内核使用,用于正确确定设备文件所指的设备,可以通过设备号获得一个指向内核设备的直接指针。
file_operations structure :
struct file_operations {
struct module *owner;//<linux/module.h> => THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int);
//loff_t is long long //via __kernel_loff_t;
//change current read/write //position.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
/* ssize_t viaing __kernel_ssize_t is defined long,
通常这个函数返回从设备读来的字节数,
如果是NULL,则会返回 -EINVAL("Invalid arguments")
*/
ssize_t (*aio_read)(struct kiocb *,char __user *, size_t, loff_t);
//异步读
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
/*返回一个非负的整数(long long ),表示已经向设备发送了多少字节,if NULL, return -EINVAL;
*/
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t *); //异步写方式
int (*readdir) (struct file *, void *, filldir_t);
/*它是被用来读管道,并且仅仅用于文件系统,对于设备文件,仅仅使用NULL*/
unsigned int (*poll) (struct file *, struct poll_table_struct *);
/*这个方法被用于三个系统调用,poll,epoll,select,询问一个读或写的文件描述符是否堵塞,if a driver
leaves its poll method NULL,这个设备被认为可读可写
*/
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
/*这ioctl系统调用提供了一种方式去实现设备的特殊命令,例如格式化软驱磁道,如果不存在
这样的ioctl方法,系统将会返回-ENOTTY("NO such ioctl for device")
*/
int (*mmap) (struct file *,struct vm_area_struct *);
/*
mmap 被用来映射设备内存到进程地址空间,如果这里是NULL,那么返回 _ENODEV
*/
int (*open) (struct inode *, struct file *);//first operation,but...
int (*flush) (struct *);
/*目前,很少使用flush,如果它为NULL,kernel will ignore user application request.
*/
int (*release) (struct inode *, struct file *);
/*激活file数据结构*/
int (*fsync) (struct file *,struct dentry *, int);
/*This method is the back end of the fsync system call,which a user calls to flush any
pending data,if this is NULL, return -EINVAL*/
int (*aio_fsync)(struct kiocb *, int);//异步
int (*fasync) (int, struct file *, int);
/*这个方法被用来通知设备去改变它的FASYNC的标志,这是关于一个异步的问题*/
int (*lock) (struct file *, int ,struct file_lock *);
/*loch 方法被用来实现文件锁,locking对于日常文件很重要,但是对于设备驱动
却几乎没有用过*/
ssize_t (*readv)(struct file *, const struct iovec *,unsigned long,loff_t *);
ssize_t (*writev) (struct file *,const struct iovec *,unsigned long,loff_t *);
/*实现扫描/采集 读或写 操作,if is NULL,被read和write代替*/
ssize_t (*sendfile) (struct file *, loff_t *,size_t, read_actor_t, void *);
//实现 sendfile系统调用,从一个文件描述符移动数据到另一个。=> NULL
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int ;
/*被内核用来发送数据一次一页同上面一样*/
unsigned long (*get_unmapped_area)(struct file *,
unsigned long, unsigned long, unsigned long, unsigned long);
/*NULL??*/
int (*check_flags)(int);
/*检查传递给fcntl的标志*/
int (*dir_notify) (struct file *, unsigned long);
/*当一个进程使用fcntl去要求目录改变,仅用来用于文件系统*/
};
这个scull设备驱动被初始化如下:
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioptl,
.open = scull_open,
.release = scull_release,
};
hints:
对于结构体
struct a {
int a;
int b;
}
有以下几种初始化方式:
struct a a1 = {
.a = 1,
.b = 2,
};
或者
struct a a2 = {
a:1,
b:2,
};
or
struct a a3 = {
1,2
};
内核喜欢用第一种方式,使用第一种和第二种方式,成员初始化的顺序可以改变。使代码更具有可移植性。
struct file 是第二重要的数据结构,与用户空间的FILE完全没有关系
mode_t f_mode;//mode_t is defined as __bitwise__
/*The method 区分这个文件是可读的还是可写的*/
loff_t f_ops;
/*目前读和写的位置*/
unsigned int f_flags;
/*这是文件标志,such as O_RDONLY,O_NONBLACK,O_SYNC,当一个驱动询问是
否正在堵塞应该检查O_NONBLOCK标志位,所有的标志是被定义在<linux/fcntl.h>*/
struct file_operations *f_op;
/*这是关于文件的,这一块很重要,但是我没有读懂什么意思,open操作
对应主设备号1,filp->p对应次设备号*/
void *private_data;
/*这open系统呼叫在使用open方法之前设置了NULL??,要记得释放*/
struct dentry *f_dentry;
/*the directory entry (dentry) 和文件有关系,BTW,设备从来不创建file结构*/
The inode 是被内核内部用来代表文件,单个文件可以有多个file结构,但是它们全都指向一个 inode,现在先有两个重要域关于驱动的
dev_t i_rdev;
/*对于inode来说,它代表设备文件,包含实际的设备号*/
struct cdev *i_dev;
/*struct cdev 是代表字符设备的内部结构*/
最近,有开发者希望能从inode中直接获取主设备号和次设备,
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
在linux中,内核使用 cdev 结构去表示字符设备,那么必须包含<linux/cdev.h>,可以使用以下方法在运行时获得 cdev 结构,
struct cdev *my_cdev = cdev_alloc();
my_dev->ops = &my_fops;
在初始化中,
void cdev_init(struct cdev *cdev, struct file_operations *ops);
在kenel呼叫cdev是
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num是这个设备对应的第一设备号,count是设备号对应的设备的数量,多数情况下是1,也有例外的情况.这个函数失败的话会返回一个负数.
To remove a char device from the system ,call:
void cdev_del(struct cdev, *dev);
scull 代表被一个scull_dev设备
struct scull_dev {
strcut scull_qset *data; /*first quantum*/
int quantum; /* The current quantum size*/
int qset; /*the current array size*/
unsigned long size; /*amount of data stored here*/
unsigned int access_key;
struct semaphore sem; /* mutual exclusion semaphore*/
/*互斥信号量*/
struct cdev cdev;/* Char device structure*/
}
struct cdev是设备加载到内核的接口,这个结构必须初始化和加进系统使用如下的方法,
static void scull_setup_cdev(struct scull_cdev *dev, int index)
{
int err, devno = MKDEV(struct scull_major, scull_mirror +
index);
cdev->init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MOUDLE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno,1);
if(err);
printk(KERN_NOTICE "error %d adding scull%d",err,index);
}
__open__method is provided for a driver to do any initialization in preparation for later operations,must consider following tasks:检查设备特别的错误,如没有就绪 Initialize the device if it is being opened for the first time Update f_op pointer,Allocate and in filp->private_data
第一件事是确定哪个设备正在被打开,open的函数原型为
int (*open)(struct inode *inode, struct file *file)
我们希望scull_dev结构包含cdev结构,(它这是在说什么), container_of defined is in <linux/kernel.h>
container_of(pointer, container_type, container_field);
这个宏把一个指针指向container_field的域,返回一个指针指向containing结构,在scull_open
struct scull_dev *dev; /*device information*/
dev = contain_of(inode->i_cdev,struct scull_dev, cdev);
filp->private_data = dev;
只要一发现scull_dev结构,scull把一个指向前者的指针储存在private_data( in file structure)
另一种方法是储存在 inode 结构体中的次设备号,尤其你使用register_chrdev方法注册设备,scull_open的代码如下:
int scull_open(struct inode *inode, struct file *file)
{
struct scull_dev *dev; /* device information*/
dev = contain_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data =dev;/*for other methods */
/*now trim to 0 the length of the device if open was write
-only*/
if ( (filp->f_flags & 0_ACCMODE) == 0_WRONLY) {
scull_trim(dev);/*ignore errors*/
}
return 0; /* success*/
}
scull is a global structure.
这个函数的作用有两个:1.撤销任何已经分配给filp->private_data的数据,2. 最后一次关闭设备
int scull_release(struct inode *inode, struct file *file)
{
return 0;
}
因为没有硬件所以可以使用最简洁的方法去关闭scull.
如果一个设备文件关闭的次数比它打开的次数多会发生什么? (what happens when a device file is closed more times than it is opened),after all,dup和fork呼叫这open files不使用open,How to a driver know when an open device file has really been closed?
the answer is simple: not every close system call causes the release method to be invoked.the kernel keeps a counter of how many times a file structure is being used.Neither fork nor dup creates a new file structure (only open does that),They just increment the counter in the existing structure.The close system call executes the release method only when the file structure drops to 0,the guarantees that your driver sees only one release call for each open.
kernel automatically closes any file at process exit time by internally using the close system call.
##scull’s Memory Usage
two core functions used to manage memory in the Linux kernel.defined in <linux/slab.h>
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
其中,size 是申请的字节大小,flags 在这里是GFP_KERNEL(又是一个多重的宏定义),稍后再详细解释这个东西.只要不使用kmalloc,就别使用kfree,当然,NULL可以传递给kfree.kmalloc 不是最有效的方法去申请内存,但这里只是为了简单展现是read和write的方法,申请整页的方法以后讲.
这里有好好几种方法改变quantum和quantum set的大小:1,改变宏SCULL_QUANTUM和SCULL_QSET.2.在加载模块的时候指定scull_quantum和scull_qset的大小.3.或者改变ioctl的大小.
struct scull_qset {
void **data;
struct scull_qset *next;
}
下面的代码段展示了struct scull_dev和struct scull_qset如何持有data,__scull_trim__被scull_open激活.它遍历list和free quantum和quantum set.
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset;
int i;
for (dptr = dev->data;dptr; dptr = next){
/*all the list items*/
if(dptr->data){
for(i=0; i<qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
ssize_t read (struct file *flip, char __user *buff,
size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff,
size_t count, loff_t *offp);
这里的buff是用户空间的数据,不能直接被kernel 的代码使用,为了能够在用户空间和内核空间传递buff,我们引入了特别的函数<asm/uaccess.h>,
unsigned long copy_to_user(void __user *to,
const void *from,
unsigned long count);
unsigned long copy_from_user(void *to,
const void __user, *from,
unsigned long count);
需要注意的是,用户空间的地址可能不存在,这时你必须把该进程睡眠.你必须保证传递给这些函数的用户空间指针是合法的.:
A negative value is means an error,defined in <linux/errono.h>.typical values include -ETNTR(interrupted sytem call) -EFAULT(bad address).
These sysyem calls are versions of read and write taking an array of structures.each of which contains a pointer and a length value.
ssize_t (*read) (struct file *filp,const struct iovec *iov,
unsigned long count, loff_t *ppos);
ssize_t (*write) (struct file *filp, const struct iovec *iov,
unsigned long count, loff_t *ppos);
Here,the filp and ppos arugments are the same as for read and write.The __iovec__structure,defined in <linux/uio.h>
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len;
};
__kernel_size_t__是unsigned long ,Each iovec describes one chunk of data to be transferred; it starts at iov_base(in user space) and is iov_len bytes long.The count parameter tells the method how many iovec structure there are.
#函数
__task_pid_nr_ns(struct task_struct *task,enum pid_type type,struct pid_namespace *ns) #文件包含 #include<linux/sched.h> #函数定义 ##在内核源代码的位置 linux-3.2.64/kernel/pid.c ##函数定义格式:
pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type,
struct pid_namespace *ns)
{
pid_t nr =0;
rcu_read_lock();
if (!ns)
ns = current->nsproxy->pid_ns;
if (likely(pid_alive(task))) {
if (type != PIDTYPE_PID)
task = task->group_leader;
nr = pid_nr_ns(task->pid[type].pid,ns);
}
rcu_read_unlock();
return nr;
}
EXPORT_SYSBOL(__task_pid_nr_ns);
#函数功能描述 此函数用于获取进程的进程号,但应该满足以下几个条件。 1.参数type如果不等于PIDTYPE_PID,则参数task用其所属任务组的第一个任务赋值,否则保持task不变。
2.此进程是参数task任务描述符的进程。
3.保证进程描述符和pid_namespaces和参数ns相同。
#参数补充(未写)
/*linux/pid.h*/
enum pid_type
{
PIDTYPE_PID, //进程的进程号
PIDTYPE_PGID, //process group' leader process ID
PIDTYPE_SID, //session' leader process ID
PIDTYPE_MAX
};
/*
*linux/pid_namespace.h
*/
struct pidmap {
atomic_t nr_free;
void *page;
}
struct pid_namespace {
/*kerf is reference count,代表此命名空间在多少进程中被使用*/
struct kref kref;
/*current system PID */
struct pidmap pidmap[PIDMAP_ENTRIES];
/*上一次分配给进程的PID的值*/
int last_pid;
/*保存指向该进程的struct_task的指针*/
struct task_struct *child_reaper;
/*指向该进程在cache中的分配空间*/
struct kmem_cache *pid_cachep;
/*初始化为0,从level内核可知进程关联多少ID*/
unsigned int level;
struct pid_namespace *parent;
#ifdef CONFIG_PROC_FS
struct vfsmount *proc_mnt;
#endif
#ifdef CONFIG_BSD_PROCESS_ACCT
struct bsd_acct_struct *bacct;
#endif
};
extern struct pid_namespace init_pid_ns;
#举例
/*
*include head file
*/
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/pid.h>
/*
*per
*/
MODULE_LICENSE("GPL");
/*
*define subprocess function
*/
int my_function(void *argc)
{
printk("<0>in the kernel thread function\n");
return 0;
}
static int __init __task_pid_nr_ns_init(void)
{
int result;
printk("<0> into __task_pid_nr_ns_init.\n");
/*
*Create a new process
*/
result = kernel_thread(my_function,NULL,CLONE_KERNEL);
/*
*get subprocess pid
*/
struct pid *kpid = find_get_pid(result);
/*
*get 进程所属的任务描述符
*/
struct task_struct *task = pid_task(kpid, PIDTYPE_PID);
/*
*获取任务对应进程的进程描述符
*/
pid_t result1 = __task_pid_nr_ns(task, PIDTYPE_PID, kpid->numbers[kpid->level].ns);
/*
*显示返回值的进程号
*
*/
printk("<0> the pid of the find_get_pid is :%d\n",kpid->numbers[kpid -> level].nr);
/*
*显示函数find_get_pid()返回值的进程描述符
*/
printk("<0> the result of __task_pid_nr_ns is :%d\n",result1);
/*
*显示函数kernel_thread()的返回值
*/
printk("<0> the result of kernel_thread is :%d\n",result);
/*显示当前进程号
*/
printk("<0> the pid of current thread is :%d\n",current->pid);
printk("<0> out __task_pid_nr_ns_init.\n");
return 0;
}
static void __exit __task_pid_nr_ns_exit(void)
{
printk("<0> Goobye __task_pid_nr_ns\n");
}
/*
*load and exit
*/
module_init(__task_pid_nr_ns_init);
module_exit(__task_pid_nr_ns_exit);
结合以前的知识,写出Makefile,执行
make
然后,键入
insmod __task_pid_nr_ns.ko
这时一般终端就会有消息产出,如果没有可以接着使用 __dmesg -c__命令 ##终端信息
Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244921] into __task_pid_nr_ns_init. Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244971] the pid of the find_get_pid is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244980] the result of __task_pid_nr_ns is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244988] the result of kernel_thread is :2915 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.244995] the pid of current thread is :2914 Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.245002] out __task_pid_nr_ns_init. Message from syslogd@debian at Nov 21 15:28:12 ... kernel:[ 458.245964] in the kernel thread function dmesg -c dmesg: klogctl failed: 不允许的操作 yubo@debian:~/linux/process$
#find_get_pid() 定义:struct pid *find_get_pid(int nr) ##功能 此函数根据提供的进程号获取对应的进程描述符,并使进程描述符的count的值加1即此进程的用户数加1
##参数说明 nr即为进程号 ##返回参数 返回与参数提供的进程号对应的进程描述符,进程描述符定义如下:
struct pid {
/*此进程的任务数*/
atomic_t count;
/*level对应成员number[]的下标,一般取值为0*/
unsigned int level;
/*此进程的任务列表*/
struct hlist_head tasks[PIDTYPE_MAX];
struct rcu_head rcu;
/*struct upid类型的数组*/
struct upid numbers[1];
};
struct uoid {
int nr;
struct pid_namespace *ns;
struct hlist_node pid_chain;
};
extern struct pid init_struct_pid;
##find_get_pid()应用举例
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/pid.h>
MODULE_LICENSE("GPL");
/*
* define subprocess
*/
int my_function(void *argc)
{
printk("<0> in the kerenl thread function!\n");
return 0;
}
/*
* load module function
*
*/
static int __init find_get_pid_init(void)
{
int result;
printk("<0> into the find_get_pid_init.\n");
/*
* create a new process, the value of return is a int num,also called
* process id
*/
result = kernel_thread(my_function,NULL,CLONE_KERNEL);
/*
* According to process id,call zhe function,get the process descriptor
* information, wait ...atomc_t?
*/
struct pid *kpid = find_get_pid(result);
/*
* how many time use the function
*/
printk("<0> the count of the pid is :%d\n",kpid->count);
/*
* level
*/
printk("<0> the level of the pid is :%d\n",kpid->level);
/*
* display PID
*/
printk("<0> the pid of the find_get_pid is :%d\n",kpid->numbers[kpid->level].nr);
/*
* display kernel_thread's return value
*/
printk("<0> the result of the kernel_thread is :%d\n",result);
printk("<0> out find_get_pid_init.\n");
return 0;
}
/*
* quit module define
*/
static void __exit find_get_pid_exit(void)
{
printk("<0> Goodbye find_get_pid");
}
module_init(find_get_pid_init);
module_exit(find_get_pid_exit);
##输出结果
Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689851] into the find_get_pid_init. Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689863] the count of the pid is :2 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689864] the level of the pid is :0 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689866] the pid of the find_get_pid is :3554 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689867] the result of the kernel_thread is :3554 Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.689868] out find_get_pid_init. Message from syslogd@debian at Nov 23 05:42:16 ... kernel:[ 3162.690635] in the kerenl thread function!
看到一篇好的!文章,转过来。
如果你认为自己是互联网中的一员(业内人士或普通用户),想想自己是不是每天都徘沉溺于各种社区和同行、朋友、同学互动?是不是时时不忘阅读各种新鲜、热辣、搞笑的文章?是不是频繁参加业内各种各样的研讨会、聚会活动,有时候还会为没赶上而懊恼?真的需要这些吗?自己从这里面真正得到收获了吗?
本文是从 What I Wish Someone Had Told Me 4 Years Ago 翻译而来,作者是一个创业者,之前在微软工作。文章主要讲述了作者对创业、做事、时间管理、面对失败的感悟,以及一些优秀的建议。读罢此文,我觉得有必要和大家一起分享作者的这些经验。文章提到的这些,至少我是比较认同的。当你开始思考这些时,你会发现,原来自己一直都陷入了一个误区——看别人表演。
嗯,是时候开始行动了!
就在2007年,我毅然离开了微软,加入了创业者的队伍。像很多第一次创业的人一样,我对这种冒险非常的兴奋。同样也跟很多第一次创业的人一样,我茫然没有头绪。
我参加各种活动,聚会,研讨会,和西雅图本地的创业社区密切交往。当我到了硅谷后,我发现这里有更多的活动,聚会,更多的研讨会。在这个创业生态系统里,一切很忙、很活跃,我收获了大量的人们共享出来的知识和体验,我如饥似渴的享用着。
我还满腔热情的看了很多的博客,视频和书籍。活动、聚会、研讨会中人们分享和讨论的都是非常好的主题。
我甚至还加入了一个创业孵化机构!
直到决定开办自己的公司时,我才意识到,所有我的阅读、观看、参加的活动实际上没有给我带来任何的用处。我是认真的。完全没有任何用处。大部分我学到的东西都想不起来了,剩下一点能记住的也很少能应用到我现在的处境里。学习他人的经验和成功对我就像是在吃快餐,像是吃那些味道很好的垃圾食品,让我膨胀,让我自满。抱歉我在泼冷水,但这是事实。
起初,我努力把我学到的东西应用到我自己的问题里。不灵。这不可思议的时刻真的到来了,我平静的接受了这个事实:我浪费了大量的时间去学了一些根本不需要的东西,我相信有人说过的一句话正是在嘲笑我的这些行为:所有的这些活动,研讨会,博客文章都只是用来多拖延你一天立即行动的另一个借口。我默默的接受这个事实,以一个新手的心情往前走,沿着这个方向,我相信我将会理解出什么才是我需要的。
所有的变化由此而来。
我们每个人身上都有一种东西,想在这种存在着不确定性、具有风险的创业冒险活动中创造,成就,前进。但我们还有另外一种东西,每个人都希望能感到确定,可靠,让我们能做出正确的决策,让我们在这条道路上不至于失败、使自己受伤。矛盾冲突就是从此产生的。
可是这些博客,活动,访谈并不能真正的消解这些矛盾。只是暂时的,它让我知道,有很多人在做和我同样的事情,让我放松。去创业是让我走出我的安逸环境,而我所做的只是从一个安逸环境跳到另外一个安逸环境。你知道吗?我就职于一个不错的公司!
一天,我反省一下我目前的状况,发现自己忙于很多事情,唯独没有把时间用于自己的产品上。几个月后,我终于鼓足勇气对自己说:这唯一重要的事情是真正的坐下来去做事。
别误会。我觉得有些博客和研讨会还是有价值的。但除非你真正的去实施什么事情、给你所学所扩展的关系网创造一个用武之地,你就是在浪费你的宝贵时间。
下面就是一些帮助了我去克服这“创业困惑综合征”的东西:
1、我数周内不再阅读创业新闻和博客,我意识到对于我的产品我不缺任何东西。至于谁又获得了融资,谁被收购了,以及为什么在Google Chrome的竞争下IE丧失了它的市场份额,这跟我没有任何关系。我唯一要关心的人是客户,我唯一要关心的事情是他们的需求和愿望、如何提供他们最有价值的东西。
2、我数月不去参加那些创业相关的活动,而且开始通过咖啡和酒去交接朋友。每月我仍可能会去参加一两个活动,但那只是为了娱乐。我不再迷惑于为了成为一个企业家而且参加那些创业相关的活动。
3、我通过小项目来实践学习。我把一些想法细化成容易管理的细目,给自己设定期限去完成它们。项目和试验品是神奇的教学工具,因为你在学习你想要的,你在获得第一手资料。就像Keynotopia这个项目极大的帮助了我产生灵感,形成思路,我可以看见它,和它交互,展示给别人——这就是我首先要把它做出来的原因!有时一些小项目还可以让你获得不少收入。
4、每走一步,我都准备好一个问题清单,它能让我知道下一步该往哪里走。是该去获得更多的访问量?改进产品?还是在不提高访问量的情况下提高收入?我整理出最好的问题,然后研究它,咨询人们,然后把获得的答案立即付诸于行动。这些信息我都不会让它们在我的大脑里转的太久。
5、这是我的最爱:我更多的恐惧是产生于没有去行动,而不是相反的害怕去行动。我认识到,晚一天我没有让客户接受我的方案,竞争对手就多一天的机会比我先达到客户要求。我甚至设想到了如果我不能行动起来,最终将会看到最可怕的噩梦:从办公室又回到密尔顿,在Innotech的小隔间里饱食终日,拿着那个红色的订书机,等着下一次的发薪日。这正是我需要的充满魔力的紧迫感。
6、我第一是要把事情完成,然后才是把事情办对。我知道(深受教训),做事的劲头重要无比。如果你在得到一个想法后不能立即付诸实施,最终很有可能的结局是这个想法被束之高阁。如今不论何时我得到了一个想法,我会盯着它让它成为现实,让它变成实际。我立即行动,虽然方式不成熟,但之后想办法改进,这样就学会了自己真正需要的东西。
7、面对现实:如果你不走出你的安逸环境、动手去干,什么事情都不会发生。等待再等待,罕有行动。
我想留给你们一句改变了我的生活的名言:成功的人并不是一定比别人更有才智和更幸运。他们只是在不断的尝试、不断的失败,直到成功。
不要做想象中的企业家,用行动和成果成为企业家。
不知道各位是否体会到了作者的用心,他用他的经历告诉我们,只有真正坐下来做事,才是取得成功的唯一途径。说,没有用;行动,才是最重要的。
与君分享
目前我能想到的是做一个有关咨询的网站,目标是社会中产阶级,利用自己的优势去帮助 他们更好的理解国家政策法规、海外的投资机会,应该不是什么难的项目。
从邮件列表开始
原文地址 c语言中,任何变量都有一个地址,这个地址是由0和1二进制代码组成的,在声明变量 时,例如int,系统就会临时开辟一个存储空间,空间的大小根据类型的不同 而不同,最直接的表现就是分配的字节数不同。这个地址是硬件访问的依据,我们在源 代码定义的变量只不过是为了方便我们自己而定义的。 例如,定义如下:
int a; float b; double c; long double d;
假设它们所占的字节分别是4、8、8、10,而且连续存储一段空间地址,起始地址是100,则我们可以得到如下分布的东东:
|_a_| 100 |___| |___| |___| 103 |_b_| 104 |___| |___| |___| |___| |___| |___| |___| 111 |_c_| 112 |___|
为了节约空间我们不画出c,当然上面的阿拉伯数字就是地址了,不过在实际的内存中 ,是一连串的0和1,但思想是差不多的。所有这些类型都是编译器告知的,由于前面我 们已经定义了int型,则编译器知道从a的开始地址往后取4个字节再把它解释成int型( 等等,这里的意思是虽然编译器知道这个变量是多少字节,但机器访问的时候并不知道 ?,就感觉矛盾,前面既然只取了4个字节,为何还要再来一次呢)。那么(float)a,就 是先按照int类型取出该值,再将该值按照int to float的规则转换成float类型,一句 话总结就是,强制类型转换就是先按照原先的类型取出该值,然后按照** to **的规 则进行强制转换,如果是(类型名)常数这种形式,则是将该常数to类型的方式强制转换。
还有,变量名与内存空间的关联关系不是由硬件指定的,而是由编译器为我们实现的。
指针也是一个变量,它自己占据一个四字节的地址空间(因为程序的寻址空间是2^32, 即4GB,所以指针的4字节大小完全够用),它的值是另一个东西的地址,这个东西可以 是普通变量、结构体、还可以是函数等,由于指针的大小是4字节,所以,我们可以将 指针强制转换成int类型或其他类型,同样,我们也可以将常数转换成int型再赋值给指 针。所有指针的空间的大小都是4字节它们只是声明的类型不同。
指针to指针的强制转换是将指针所指的内容的类型由原来的类型转换为后面的类型。
int a = 1;
int *p = &a;
float *p1 = (float *)p;
则p和p1的值都是&a,但是p是将&a地址中的值按照int型变量进行解释,而p1则是将& a地址中的值按照float型变量进行解释。
ANSI C规定,void指针可以复制给其他任意类型的指针,其他任意类型的指针也可以 复制给void指针,他们之间复制不需要强制类型转换。当然任何地址也可以复制给void型指针。
自己原来在指针这块这么差啊,受不鸟自己了。
姑且这么理解,自己在网上看到一个比较的例子:
int a = 8;
int *q;//指针(q)的类型是int *,指针指向的类型是int
q = &a;//给q赋值,但这里两者可以合写 *q = &a,仔细想想概念细节;
/*即使int *q = &a,我们最好将事实上也就是把q的类型看成int *,这样才会与&a的事
实联系起来*/
printf("%d\n",*q);
/*结果就是8*/
printf("%d\n",q);
/*结果就是a变量的地址,同&a的结果是一样的*/
float f = 12.3;
float *fptr = &f;
int *p;
假如我们想让指针p指向浮点数f,那么就是p = &f吗,错,因为两边的类型不同,不能 直接赋值,需要强制转换。
p = (int *)&f;
如果有一个指针p,我们需要把它的类型和它所指向的类型改为TYPE *和TYPE,那么语法格式就是(TYPE *)p.
那么我们可不可以把一个整数当作指针的值直接赋给指针呢?就像下面的语句:
unsigned int a;
TYPE *ptr;//TYPE可以是int,char或结构体等类型
...
...
a = 12345678;
ptr = 12345678;//我们的目的是要使指针指向地址12345678(十进制)
ptr = a;
/*不用说,后面的两条语句是错误的,但我们可以使用下面的语句*/
a = 12345678;//必须是合法的地址
ptr = (TYPE *)a;
/*这样就可以实现我们的目的,这里(TYPE *)a的意思是把无符号整数a的值当作一
个地址来对待,还有,我们可不可以反过来,即把指针指向的地址即指针的值当作一个
整数取出来,完全可以,下面的例子演示了把一个指针的值当作一个整数取出来,然后
再把这个整数当作一个地址赋给一个地址。*/
int a = 123,b;
int *ptr = &a;
char *str;
b = (int)ptr;//把指针ptr的值当作一个整数取出来
str = (char *)b;//把这个整数的值当作一个地址赋给指针str
int m;
int *pm = &m;
char *cp = (char *)&m;
pm指向一个整型,cp指向整型的第一个字节。
struct str1 a;
struct str2 b;
a = (struct) b; //This is wrong
a = *((struct str1*)&b); //This is corrent
这一部分的缺陷继续去阅读经典的书籍去填补
一个结构体定义type,这个结构体中某个成员变量的名字member以及它的地址ptr
包含此成员变量的结构体的地址
struct father_t {
int a;
char *b;
double c;
}f;
char *ptr = &(f.b);
/*而不是 ptr = f.b; 这里ptr是结构体f的成员b的地址,而不是它指向的地址。
根据C语言对struct类型的存储特性,我们可以画这么一个图示:
*/
}
0 +------------+ <----我们要求的是这个地址 |a (4 bytes) | 4 +------------+ <----我们知道这个地址 |b (4 bytes) | 8 +------------+ |c (8 bytes) | 16 +------------+
通过以上分析我们不难得出,我们只需要把当前知道的成员变量的地址ptr,减去它在结构体当中的相对偏移量4就得到了结构体的地址(ptr-4)。在linux内核中,有一个很好的宏叫做container_of,
#define offset(TYPE,MEMBER)((size_t) & ((TYPE *)0)->member)
问题是,你能清楚的讲解上面语句的含义吗?我用了一天的时间去恶补基础,结果发现自己太菜了。
1.((TYPE *)0) 将零强制转换为TYPE类型的指针;
2.((TYPE *)0)->MEMBER 访问结构中的数据成员;
3.&(((TYPE *)0)->MEMBER) 取出数据成员的地址,即想对于0的偏移量,求的就是它。
4.(size_t)(&((TYPE *)0)->MEMBER) 结果强制转换,size_t应该最终为 unsigned int 类型。
#include<stdio.h>
#define offset(TYPE,MEMBER)((int)(&((TYPE *)0)->MEMBER))
struct _test_{
char x;
int y;
float z;
};
int main()
{
int temp = -1;
temp = offset(struct _test_,z);
printf("temp = %d\n",temp);
return 0;
}
结果是 8.
原本是想看Linux Kernel内核的,结果… 这篇文章纯属自己回忆瞎写,请不要当真。
struct tag { member-list } variable-list;
上面的粗体至少有部分;不知道c语言的发明者为什么会添了个标签,按照书上的解释,标签(tag)可以为成员列表提供一个名字,这样它就可以在后续的声明中使用,而且多个声明共同使用一个成员列表,并且创建同一种类型的数据结构。
struct {
int a;
char b;
float c;
} x;
这个声明创建了一个名叫x的变量,它包含三个成员:一个整数、一个字符和一个浮点。
struct {
int a;
char b;
float c;
} y[20],*z;
这个声明创建了y和z,y是一个数组,它包含了20个结构,z是一个指针,它指向这个类型的结构。上面两个声明会被当作两种截然不同的类型。 ##标签的使用
struct SIMPLE {
int a;
char b;
float c;
};
我们以后想快速创建含有类似结构的结构体变量时,可以这样使用: struct SIMPLE x;
struct SIMPLE y[20],*z;
并且它们具有相同的类型。
使用结构体时,我们有一个东西不得不说:typedef;请看下例:
typedef struct{
int a;
char b;
float c;
} Simple;
区别在于现在的Simple是一个类型名而不是标签了,我们可以这样使用:
Simple x;
Simple y[20],*z;
同上面使用标签是一样的。
关于字节对齐,根据机器类型来讲,有两类:一是内存上的,二是栈上的。为什么使用 对齐,主要从存取效率上考虑的,以某个整数的倍数为起点(也就是边界对齐)取一次数 据用一个?周期,但是如果不对齐的话则需要两次。continue….
最近想写linux设备驱动,发现不同参考书上的Makefile文件风格不一致,不一致是小事,我自己对这方面知识的欠缺总感觉穿着很重的鞋子走路。当然,网上的资料也是良莠不齐,自己看内核代码Document/kbuild中的文件算是做一下读书笔记,有错误的话还请谅解。
“kbuild” is build system used by the linux kernel.Modules must use kbuild to stay compatible with GCC. Modules programming is consist of in-tree and out-of-tree
$make -C
M=$PWD $make -C /lib/modules/\`uname -r\`/build M=$PWD
target:
modules_install
$make -C /lib/modules/`uname -r`/build modules_install
tips: $KDIR is short of path of the kernel source directory
-C $KDIR make命令会自动改变到这个特殊的kernel source目录
M=$PWD 通知kbuild一个外部模块已经被建立,”M”是这个外部模块(kbuild file)所在的绝对路径
make -C $KDIR M=$PWD [target]
模块默认在这个文件夹生成(哪个?),所有的文件都会在这个文件夹中
同上
同上
仅仅删除在模块目录中生成的所有文件
从来没用过
最简单的例子:
obj-m := <module_name>.o
这kbuild系统将会根据
如果模块是由多个文件构建而来,则是一下格式
<module_name>-y := <src1>.o <src2>.o