| 個人檔案无声相片部落格清單 | 說明 |
无声无声无息的,然后无声无息的 12 March 关于C语言及面向对象编程 一 前言 说到面向对象,我们似乎总会在第一时间想到
Java、C++、Python。对,这些都是提供了面向对象支持的语言。但事实上,面向对象就是语言本身吗?稍有点编程经验的人都会有一个否定的答案。
但为什么之前所述的那些语言能称之为面向对象的语言?如果语言本身并不是答案,那用非面向对象的语言,比如C,也应该能够写出面向对象的代码了。我并不是
想发表什么高见,我只是想把最近看到了一些东西做一个比较系统的记录而已。同时也希望自己能够时时记住这些方式,写更漂亮的code。 二 面向对象
什么是面向对象?似乎我还没有能力去下这个定义。是Class?Object?还是Interface?似乎都是。在我看来,也正是因为语言本身从语法的
级别开始就支持这种概念,才使之有资格成为一种面向对象的编程语言。但当然这还不足以完全地定义一个面向对象的语言,因为这只是从封装的概念来描述面向对
象的概念。 那然后呢?然后应该是多态。重载是一个重要的方面,它使得同样的接口在不同的对象中出现不同的行为。这样的特性其实源于封装, 不单只把对象的状态封装起来,还把对象的接口也包含在内。正是因为这种封装形式的存在,才使的基于接口的程序设计方式能够在这个时代大放异彩。这样的特性 带来了两个好处:代码重用和解偶。首先,把代码以类的形式封装,使得代码能够以一种更具逻辑性的方式在不同的地方得到重用。函数本身也是一种重用的概念, 但它本是跟它所需要处理的数据没有显著的语法层次上的关联,类则使得方法与数据有机地组合在一起。因此,在重用的概念上,类不但重用了逻辑,还重用了逻辑 所处理的数据。因此在面向对象的领域中,代码得到了更大程度的重用。关于怎么封装代码,大家可以参考“敏捷”、“测试驱动”之类的书籍。另外,现代程序设 计更倾向于写“羞涩的”代码,也就是说,把内部的逻辑私有化。但同时它还有另外一层意思,不单只是类内部的逻辑不应该被外部所了解,就算是类本身的逻辑也 应该分割成相互独立的部分,也就是说把代码正交化。程序的逻辑并不是永恒不变的,一旦需求发生了变化,环境出现的修改,代码本身也需要作出响应。正交的代 码能使得这些改动变得容易实现,并把修改范围最小化。很大程度上,这些方法有多源于封装的概念。关于怎么写“羞涩的”代码,大家可看看“设计模式”、“重 构”一类的书籍。 关于面向对象,应该会有更加全面描述,但我经验不多,阅历也浅,不好再为这种模糊的东西多费笔墨了。所以,此时此刻,我想面向对象的核心可能就是一个概念:封装。什么是封装?我们可以简单回顾一下历史。 Once upon a time。计算机科学的前辈们曾经用纸带来表示一条条指令,把它放到计算机里面,然后计算机就有了一些基于二进制的动作。比如说把地址线电平变成 10100110,然后从数据线把10100110这个内存字节上的内容取出来。这个时候,我们可以认为这些指令其实没有任何形式的封装。(哦,可能还是 有的,比如说内存芯片自己会自动把10100110上的内容放到数据总线上。)这个时候,每个动作都是一条指令,我们要从内存中读取数据,那起码需要两个 动作:指定一个地址和读取一个数据。 后来,时代不同了,我们有了汇编。这个时候,我们读取一个内存数据,我们只需要用一条指令,比如:mov A, #0xa6。很好,我们把机器碼封装成了更加有意义的指令。我们还可以把一堆指令划分成不同的逻辑模块,然后通过一个call指令来调用。酷,我们还把一 堆指令封装成函数了!但数据呢?似乎数据本身还没有任何意义上的抽象支持。 好,C用什么办法帮我们组织数据呢?类型!好,我们看到了已经不是0xa6上的一串0101010了,起码我知道它应该代表一个字符char*或者是一个 数字int。我还可以帮这些数据起名字!不仅如此,我们还有struct跟tepydef来定义新的类型!太酷了!于是,我们把程序的数据变得更加有意义 了。可能就只是C被成为结构化的编程语言的原因吧。 但人是不会轻易满足的。C好是好,但还不够好。第一,C的数据结构细节在语法上是无法隐藏的,这意味着任何人都可以访问,人们常常会因为便利的原因用一些 跟这个数据结构关系不强的代码来访问结构的字段,造成了各种各样的模块耦合。第二,每一个功能类似,操作的数据相近的函数都需要重新定义,代码可以用函数 的方式重用,但接口确用重新定义,导致调用者也要做相应修改,基于接口的解偶没有办法很好地操作。这都导致了用C写程序常常有牵一发而动全身的感觉。 好,到此,我已经把我想说的关于面向对象的一些背景说完了,不知道对不对,但不论怎样,我们先来看看有此得到的一些面向对象的原则: 1. 面向对象需要封装。数据结构内部成员不能任意访问。 2. 要有多态,接口不应该改变。 3. 要能继承,要让代码更具正交性。 4. 最根本的,还是封装。不但要封装数据,还要封装行为。 胡言乱语了一些关于面向对象的理解,可能并不正确,大家见笑了。
正如之前所说,面向对象应该是一些列的编程原则,所以就算用非面向对象的语言,也一样能写出面向对象的代码。C本身并没有在语法层次上对面向对象的原则进
行支持,但运用一些技巧之后,C程序也一样可以变得很OO(object
oriented)。GObject是我所见过的最完整的C语言面向对象框架,只要按照他所规定的原则编程,从封装到多态,甚至是对象初始化,这个过程都
变得自动化。但事实上,这个框架引入了较多复杂性,用起来有时候相当繁琐(之所以引入这些复杂性,设计者的目的是要向其他动态语言导出API)。如果我们
把对象初始化的部分忽略,这个框架就简洁多了。但下文描述的可能更加简炼,只是以小case的形式吧一些技巧(习惯)描述出来而已。 1. class与struct C
里面没有class,而跟class最像的莫过于struct。这两个关键字都可以用来封装数据,但稍有不同。struct可以把不同的字段封装在一起,
通过跟typedef一起使用,可以用来定义新的数据类型。但它的缺陷也显而易见:没有访问控制。事实上,这样一个问题可以通过命名来解决,这是
Linux内核Device Driver
Model的处理方式。用"__"(双下划线)开头变量代表是private的,存在protected与否似乎并不太关键,毕竟我们不是真的能够支持承
继,当然你也可以用“_“(单下划线)来前缀你的protected成员。其他命名方式建立的变量则是public的: 2. 函数指针与多态struct demo{ char *name; //public member int __age; //private member int __salary; //private member } 还有一种更加彻底的方式: struct private_demo { int __age; int __salary; } struct demo { struct private_demo priv; //private member char *name; //public member } 但我总觉得第二种方式有点过火了,细节是被隐藏了,但没有语法上的支持跟编译器的检查,其实要访问这个priv里面的东西还是可能的,既然这样,何必多次一举呢?其实从命名上给予自己或者其他的开发人员以足够的警告就可以了。 class跟struct另外一个不同是对方法的封装。class可以定义类的接口,而struct没有这种支持。但我们还是可以接用函数指针来完成这个任务: struct demo { int (*intf_a) (int, char *); //member function void (*intf_b) (void); char *name; //public member int __age; //private member } 但个人觉得运用Linux kernel的方式更能强迫你把接口考虑清楚: struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); 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); int (*check_flags)(int); int (*dir_notify)(struct file *filp, unsigned long arg); int (*flock) (struct file *, int, struct file_lock *); }; static struct file_operations skel_fops = { .owner = THIS_MODULE, .read = skel_read, .write = skel_write, .open = skel_open, .release = skel_release, }; //注意,这里用的是GCC的语法扩展,如果没有这种扩展,我们需要自己赋值: //static struct file_operations skel_fops; //skel_fops.read = skel_read;...... struct usb_class_driver { char *name; struct file_operations *fops; mode_t mode; int minor_base; }; static struct usb_class_driver skel_class = { .name = "usb/skel%d", .fops = &skel_fops, .mode = S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, .minor_base = USB_SKEL_MINOR_BASE, }; struct file_operations在这里充当了java里面interface的角色。
虽然函数指针能够把struct封装得更像一个class,但事实上我们也没有必要拘泥于面向对象的形式,要记住,面向对象是一套原则或者是一种思想。对
于C来说,我们也没有必要非得把它弄成C++不可,何况,它再怎么像也不可能是C++。所以,我们不需要把所有的函数都封装进所有的struct,要建设
有C语言特色的面向对象编程方式。哈哈。
四 C中的“是”与“有”那什么函数应该放进struct呢?刚刚我们也提到了一个面向对象的重要元素:接口。接口不应该在改动程序时发生变法,也就是所谓的多态。大家注意到了 吧,这于面向对象编程中虚函数的特性是一致的。也就是说,在C++或Java中的虚函数应该在C中用函数指针的方式实现。更广义的说,所有有可能在系统中 有两套或以上实现方式(重载)的函数都因该用函数指针实现。因为模块(或者说是class)外部只会调用模块提供的方法,包括public或 protected(被子类调用),这样可以使得不同的对象可以对接口有不同的实现方式,模块外的代码也不需要修改接口的调用方式(比如说函数名)。而 private的方法,则没有这个必要。而为了更好的封装,我们应该把这些方法声明为static。同时方法的命名也应该遵守成员变量的命名方式,用 "_"(单下划线)或者"__"(双下划线)前缀protected的方法。 1. “有”与“是”的区别 除了之前所说的一些编程方式,这里再记录一些关于对象承继和组合的问题。主要
的中心就是在C中实现对象编程中的“有”跟“是”。概念其实很简单,比如说车“是”一个种交通工具,它里面“有”一个引擎。但有时候这个两概念并不是那么
容易,不过这个话题还是留给系统的设计人员吧,我只在编程技巧方面来总结我所知道的。
2. C中的处理方式 关于“有”的处理方式其实很简单,看看例子就知道了: struct car { struct engine my_engine; struct wheel wheel1; struct wheel wheel2; struct wheel wheel3; struct wheel wheel4; } 或者我们可以通过指针的方式来组合: struct car { struct engin *my_engine; ..... } 这就是“有”,通过跟C++中差不多的方式,我们就可以访问这些成员了: struct car car1; car1.my_engine...... 但关于“是”的处理就稍微严格一点,我们必须把父类结构放在新结构的第一位,并且,我还不知道应该怎么实现多重承继。这样做的好处是,我们只需要一个简单的转型就可以得到这个struct的父类或者这个继承关系中的任何一个角色: typedef struct _object { .... } object; typedef struct _vehicle { object father_obj; ..... } vehicle; typedef struct _car { vehicle father_vehicle; ..... } car; car *my_car = malloc(sizeof(struct car)); object *obj = (object *) my_car; vehicle *vhl = (vehicle *)my_car; 但注意,这里不能用指针: typedef struct _car { vehicle *father_vehicle; //This is wrong, the father object should locate in the same address of the child object. } 15 July 锁,放在里面 放在外面 很汗这个充满哲理的题目。。可惜,这是一篇技术文档。讲述的是一些数据结构加锁的问题。
在几乎所有现代编程当中,多线程和同步互斥都是一个复杂的问题。就我本人来开,我的经验并不丰富,所以这里仅仅记录我自己的一些经验,以备查阅。
同步互斥问题的一个关键不份是对数据加锁。也就是说,我们通过锁来控制对结构的并发访问。在这里不详细讲述锁的工作原理,大家可以参考Linux Device Driver的相关章节。这里讲的是关于一个锁到底应该放在那里的问题。我按照我遇到的锁的作用,我把他们分成两类:用于控制数据的并发访问、用于控制对象的生存期。
我曾经遇到一个问题:某个结构会被动态地释放,但在这个操作进行时,可能正有其他的用户在访问这个结构。对于面向对象的语言来来说,运用observer的pattern可以很完美地解决这个问题(参考design pattern)。但就我遇到的情况而言,至少我没有找到在C下实现这种方案的方法。
下面是我所与遇问题的一个简化抽象:
struct dy_removable{
~
spinlock_t access;
} dy_removable_t
struct dev_driver {
dy_removable_t* removalbles[MAX_REMOVABLE];
}
还有很多函数从dev_driver中的removalbles[]中取出其中的removable来使用。但我们必须能够动态地把特定的removable从系统删除,具体操作要求把对应的removables[]的元素释放并并置NULL。而其他函数可能正在访问这个removalbe。为此我们为removable加入一个使用计数器use_count。每当函数需要使用这个结构时,先要获得锁access,然后对计数器加一,在使用完毕后对use_count减一,然后释放access。当我们动态删除一个removable时,我们首先申请access,使其他函数不能访问他,然后释放该锁,接着释放该removable,然后置对应的removalbes[]元素为NULL。
这样做问题显而易见,一旦我们释放了access,其他函数就有可能访问到这个removalbe。就算成功把结构释放了,但其他函数仍然会试图访问这个NULL的指针而出错。解决这个情况,我们需要另一个锁,这个锁不应该放在removable中,因为当removable被释放后,这个锁会失效。由于他用于控制removables[]的访问,我们应该把他放在dev_driver里面。有此得出的结论是,应该把管理一个结构生存期的锁放在该结构的外面,而把控制其并发访问的锁放在里面。 2 June Innocence (about the new BGM)再一次,掉进了Avril的揭嘶底里。
每次在寂静的房间让这首歌在循环,总会有放声大喊的冲动,把所有已经压抑太久太久的渴望,有心底深处呐喊出来!
I'm far to be innocent, but I still want it to be simple. And still……
It's the state of bliss you think
you're dreaming, It's the happiness inside that you're feeling, It's so beautiful it makes you wanna cry 所以,让最真实的感受在心中回荡;所以,让最深层的渴望一直呐喊;所以,做一个对自己诚实的自己…… Waking up I see that everything is ok
The first time in my life and now it's so great Slowing down I look around and I am so amazed I think about the little things that make life great I wouldn't change a thing about it This is the best feeling This innocence is brilliant I hope that it will stay This moment is perfect please don't go away I need you now And I'll hold on to it don't you let it pass you by I found a place so safe not a single tear The first time in my life and now it's so clear Feel calm I belong I'm so happy here It's so strong and now I let myself be sincere I wouldn't change a thing about it This is the best feeling This innocence is brilliant I hope that it will stay This moment is perfect please don't go away I need you now And I'll hold on to it don't you let it pass you by It's the state of bliss you think you're dreaming It's the happiness inside that you're feeling It's so beautiful it makes you wanna cry It's the state of bliss you think you're dreaming It's the happiness inside that you're feeling It's so beautiful it makes you wanna cry It's so beautiful it makes you want to cry This innocence is brilliant it makes you want to cry This innocence is brilliance please don't go away Cause I need you now And I'll hold on to it don't you let it pass you by This innocence is brilliant I hope that it will stay This moment is perfect please don't go away I need you now And I'll hold on to it don't you let it pass you by 12 May 湖面 湖面,平静,波澜不惊,映出心灵深处的乱缨;看不清,渴望的原形,原来早已动摇不定。
想抓住。猛然发现,湖面下汹涌暗潮可见,太晚!早已深陷!
唉。。。猪兜甘!
既然打破了湖面的平静,那就把乱流也扫清。能做到吗?不知道,但,我想试试。
秉着弱势的勇气,因为没有输得权利。 20 April Kernel Locking Techniques原文链接http://james.bond.edu.au/courses/inft73626@033/Assigs/Papers/kernel_locking_techniques.html Proper locking can be tough--real tough. Improper locking can result in random crashes and other oddities. Poorly designed locking can result in code that is hard to read, performs poorly and makes your fellow kernel developers cringe. In this article, I explain why kernel code requires locking, provide general rules for proper kernel locking semantics and then outline the various locking primitives in the Linux kernel.
Why Do We Need Locking in the Kernel?The fundamental issue surrounding locking is the need to provide synchronization in certain code paths in the kernel. These code paths, called critical sections, require some combination of concurrency or re-entrancy protection and proper ordering with respect to other events. The typical result without proper locking is called a race condition. Realize how even a simple i++ is dangerous if i is shared! Consider the case where one processor reads i, then another, then they both increment it, then they both write i back to memory. If i were originally 2, it should now be 4, but in fact it would be 3! This is not to say that the only locking issues arise from SMP (symmetric multiprocessing). Interrupt handlers create locking issues, as does the new preemptible kernel, and any code can block (go to sleep). Of these, only SMP is considered true concurrency, i.e., only with SMP can two things actually occur at the exact same time. The other situations--interrupt handlers, preempt-kernel and blocking methods--provide pseudo concurrency as code is not actually executed concurrently, but separate code can mangle one another's data. These critical regions require locking. The Linux kernel provides a family of locking primitives that developers can use to write safe and efficient code.
SMP Locks in a Uniprocessor KernelWhether or not you have an SMP machine, people who use your code may. Further, code that does not handle locking issues properly is typically not accepted into the Linux kernel. Finally, with a preemptible kernel even UP (uniprocessor) systems require proper locking. Thus, do not forget: you must implement locking. Thankfully, Linus made the excellent design decision of keeping SMP and UP kernels distinct. This allows certain locks not to exist at all in a UP kernel. Different combinations of CONFIG_SMP and CONFIG_PREEMPT compile in varying lock support. It does not matter, however, to the developer: lock everything appropriately and all situations will be covered.
Atomic OperatorsWe cover atomic operators initially for two reasons. First, they are the simplest of the approaches to kernel synchronization and thus the easiest to understand and use. Second, the complex locking primitives are built off them. In this sense, they are the building blocks of the kernel's locks. Atomic operators are operations, like add and subtract, which perform in one uninterruptible operation. Consider the previous example of i++. If we could read i, increment it and write it back to memory in one uninterruptible operation, the race condition discussed above would not be an issue. Atomic operators provide these uninterruptible operations. Two types exist: methods that operate on integers and methods that operate on bits. The integer operations work like this:
atomic_t v;They are simple. There are, however, little caveats to keep in mind when using atomics. First, you obviously cannot pass an atomic_t to anything but one of the atomic operators. Likewise, you cannot pass anything to an atomic operator except an atomic_t. Finally, because of the limitations of some architectures, do not expect atomic_t to have more than 24 usable bits. See the ``Function Reference'' Sidebar for a list of all atomic integer operations. The next group of atomic methods is those that operate on individual bits. They are simpler than the integer methods because they work on the standard C data types. For example, consider void set_bit(int nr, void *addr). This function will atomically set to 1 the ``nr-th'' bit of the data pointed to by addr. The atomic bit operators are also listed in the ``Function Reference'' Sidebar.
SpinlocksFor anything more complicated than trivial examples like those above, a more complete locking solution is needed. The most common locking primitive in the kernel is the spinlock, defined in include/asm/spinlock.h and include/linux/spinlock.h. The spinlock is a very simple single-holder lock. If a process attempts to acquire a spinlock and it is unavailable, the process will keep trying (spinning) until it can acquire the lock. This simplicity creates a small and fast lock. The basic use of the spinlock is:
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;The use of spin_lock_irqsave() will disable interrupts locally and provide the spinlock on SMP. This covers both interrupt and SMP concurrency issues. With a call to spin_unlock_irqrestore(), interrupts are restored to the state when the lock was acquired. With a UP kernel, the above code compiles to the same as:
unsigned long flags;which will provide the needed interrupt concurrency protection without unneeded SMP protection. Another variant of the spinlock is spin_lock_irq(). This variant disables and re-enables interrupts unconditionally, in the same manner as cli() and sti(). For example:
spinlock_t mr_lock = SPIN_LOCK_UNLOCKED;This code is only safe when you know that interrupts were not already disabled before the acquisition of the lock. As the kernel grows in size and kernel code paths become increasingly hard to predict, it is suggested you not use this version unless you really know what you are doing. All of the above spinlocks assume the data you are protecting is accessed in both interrupt handlers and normal kernel code. If you know your data is unique to user-context kernel code (e.g., a system call), you can use the basic spin_lock() and spin_unlock() methods that acquire and release the specified lock without any interaction with interrupts. A final variation of the spinlock is spin_lock_bh() that implements the standard spinlock as well as disables softirqs. This is needed when you have code outside a softirq that is also used inside a softirq. The corresponding unlock function is naturally spin_unlock_bh(). Note that spinlocks in Linux are not recursive as they may be in other operating systems. Most consider this a sane design decision as recursive spinlocks encourage poor code. This does imply, however, that you must be careful not to re-acquire a spinlock you already hold, or you will deadlock. Spinlocks should be used to lock data in situations where the lock is not held for a long time--recall that a waiting process will spin, doing nothing, waiting for the lock. (See the ``Rules'' Sidebar for guidelines on what is considered a long time.) Thankfully, spinlocks can be used anywhere. You cannot, however, do anything that will sleep while holding a spinlock. For example, never call any function that touches user memory, kmalloc() with the GFP_KERNEL flag, any semaphore functions or any of the schedule functions while holding a spinlock. You have been warned. If you need a lock that is safe to hold for longer periods of time, safe to sleep with or capable of allowing concurrency to do more than one process at a time, Linux provides the semaphore.
SemaphoresSemaphores in Linux are sleeping locks. Because they cause a task to sleep on contention, instead of spin, they are used in situations where the lock-held time may be long. Conversely, since they have the overhead of putting a task to sleep and subsequently waking it up, they should not be used where the lock-held time is short. Since they sleep, however, they can be used to synchronize user contexts whereas spinlocks cannot. In other words, it is safe to block while holding a semaphore. In Linux, semaphores are represented by a structure, struct semaphore, which is defined in include/asm/semaphore.h. The structure contains a pointer to a wait queue and a usage count. The wait queue is a list of processes blocking on the semaphore. The usage count is the number of concurrently allowed holders. If it is negative, the semaphore is unavailable and the absolute value of the usage count is the number of processes blocked on the wait queue. The usage count is initialized at runtime via sema_init(), typically to 1 (in which case the semaphore is called a mutex). Semaphores are manipulated via two methods: down (historically P) and up (historically V). The former attempts to acquire the semaphore and blocks if it fails. The later releases the semaphore, waking up any tasks blocked along the way. Semaphore use is simple in Linux. To attempt to acquire a semaphore, call the down_interruptible() function. This function decrements the usage count of the semaphore. If the new value is less than zero, the calling process is added to the wait queue and blocked. If the new value is zero or greater, the process obtains the semaphore and the call returns 0. If a signal is received while blocking, the call returns -EINTR and the semaphore is not acquired. The up() function, used to release a semaphore, increments the usage count. If the new value is greater than or equal to zero, one or more tasks on the wait queue will be woken up:
struct semaphore mr_sem;The Linux kernel also provides the down() function, which differs in that it puts the calling task into an uninterruptible sleep. A signal received by a process blocked in uninterruptible sleep is ignored. Typically, developers want to use down_interruptible(). Finally, Linux provides the down_trylock() function, which attempts to acquire the given semaphore. If the call fails, down_trylock() will return nonzero instead of blocking.
Reader/Writer LocksIn addition to the standard spinlock and semaphore implementations, the Linux kernel provides reader/writer variants that divide lock usage into two groups: reading and writing. Since it is typically safe for multiple threads to read data concurrently, so long as nothing modifies the data, reader/writer locks allow multiple concurrent readers but only a single writer (with no concurrent readers). If your data access naturally divides into clear reading and writing patterns, especially with a greater amount of reading than writing, the reader/writer locks are often preferred. The reader/writer spinlock is called an rwlock and is used similarly to the standard spinlock, with the exception of separate reader/writer locking:
rwlock_t mr_rwlock = RW_LOCK_UNLOCKED;Likewise, the reader/writer semaphore is called an rw_semaphore and use is identical to the standard semaphore, plus the explicit reader/writer locking:
struct rw_semaphore mr_rwsem;Use of reader/writer locks, where appropriate, is an appreciable optimization. Note, however, that unlike other implementations reader locks cannot be automatically upgraded to the writer variant. Therefore, attempting to acquire exclusive access while holding reader access will deadlock. Typically, if you know you will need to write eventually, obtain the writer variant of the lock from the beginning. Otherwise, you will need to release the reader lock and re-acquire the lock as a writer. If the distinction between code that writes and reads is muddled such as this, it may be indicative that reader/writer locks are not the best choice.
Big-Reader LocksBig-reader locks (brlocks), defined in include/linux/brlock.h, are a specialized form of reader/writer locks. Big-reader locks, designed by Red Hat's Ingo Molnar, provide a spinning lock that is very fast to acquire for reading but incredibly slow to acquire for writing. Therefore, they are ideal in situations where there are many readers and few writers. While the behavior of brlocks is different from that of rwlocks, their usage is identical with the lone exception that brlocks are predefined in brlock_indices (see brlock.h):
br_read_lock(BR_MR_LOCK);Use of brlocks is currently confined to a few special cases. Due to the large penalty for exclusive write access, it should probably stay that way.
The Big Kernel LockLinux contains a global kernel lock, kernel_flag, that was originally introduced in kernel 2.0 as the only SMP lock. During 2.2 and 2.4, much work went into removing the global lock from the kernel and replacing it with finer-grained localized locks. Today, the global lock's use is minimal. It still exists, however, and developers need to be aware of it. The global kernel lock is called the big kernel lock or BKL. It is a spinning lock that is recursive; therefore two consecutive requests for it will not deadlock the process (as they would for a spinlock). Further, a process can sleep and even enter the scheduler while holding the BKL. When a process holding the BKL enters the scheduler, the lock is dropped so other processes can obtain it. These attributes of the BKL helped ease the introduction of SMP during the 2.0 kernel series. Today, however, they should provide plenty of reason not to use the lock. Use of the big kernel lock is simple. Call lock_kernel() to acquire the lock and unlock_kernel() to release it. The routine kernel_locked() will return nonzero if the lock is held, zero if not. For example:
lock_kernel(); Preemption ControlStarting with the 2.5 development kernel (and 2.4 with an available patch), the Linux kernel is fully preemptible. This feature allows processes to be preempted by higher-priority processes, even if the current process is running in the kernel. A preemptible kernel creates many of the synchronization issues of SMP. Thankfully, kernel preemption is synchronized by SMP locks, so most issues are solved automatically by writing SMP-safe code. A few new locking issues, however, are introduced. For example, a lock may not protect per-CPU data because it is implicitly locked (it is safe because it is unique to each CPU) but is needed with kernel preemption. For these situations, preempt_disable() and the corresponding preempt_enable() have been introduced. These methods are nestable such that for each n preempt_disable() calls, preemption will not be re-enabled until the nth preempt_enable() call. See the ``Function Reference'' Sidebar for a complete list of preemption-related controls.
ConclusionBoth SMP reliability and scalability in the Linux kernel are improving rapidly. Since SMP was introduced in the 2.0 kernel, each successive kernel revision has improved on the previous by implementing new locking primitives and providing smarter locking semantics by revising locking rules and eliminating global locks in areas of high contention. This trend continues in the 2.5 kernel. The future will certainly hold better performance. Kernel developers should do their part by writing code that implements smart, sane, proper locking with an eye to both scalability and reliability. 5 April 关于ubuntu的开发环境开发环境 reference: https://help.ubuntu.com/community/InstallingCompilers GCC、G++、GNU/Make等编译器在build-essential包中,开发文档在manpages-dev中。 内核编译 reference: http://www.howtoforge.com/kernel_compilation_ubuntu kernel-package libncurses5-dev(meak menuconfig) fakeroot(make-kpkg clean fakeroot make-kpkg --initrd --append-to-version=-custom kernel_image kernel_headers) wget(get kernel) bzip2(patch) (估计于这两个步骤有关dpkg -i linux-image-2.6.18.1-custom_2.6.18.1-custom-10.00.Custom_i386.deb dpkg -i linux-headers-2.6.18.1-custom_2.6.18.1-custom-10.00.Custom_i386.deb) 更新资源 reference: http://wiki.ubuntu.org.cn/%E5%BF%AB%E9%80%9F%E8%AE%BE%E7%BD%AE%E6%8C%87%E5%8D%97/DapperDrake Mirror.lupaworld.com 更新服务器(浙江省杭州市电信,亚洲地区官方更新服务器,推荐全国用户使用。) deb http://cn.archive.ubuntu.com/ubuntu dapper main restricted universe multiverse 19 March farewell ball. 又是一篇隐晦得东西,看不懂请原谅,只是心里面有些事情,不吐不快。
farewell 波仔,我会记得和你在球场上一起流汗日子,也会记得我们班和你们班两次难忘得对碰。当然,绝对不会忘记你很有特征得双眼,永远是一条线,感觉就象永远在笑。的确,无论是7人赛我们惜败给你们1:0的时候,还是5人赛上演那场经典逆转,最终把你们挡在淘汰赛外的时候,你都在笑。的确没有什么难过的,球场上就是用汗水来论英雄,不论胜负。在球场上,所有跟你我一样热爱足球的兄弟姐妹,就连泪水也是甜的。
大家已经约好了,我们回去,一起送你,最后一次。本来还打算帮你报名的这次五人赛,准备的很仓促,不过,我们会尽力的,This time, we fight for ball。这次,最后一次,我们在足球场上跟你并肩挥汗,大家一定带上你的笑容,一定。
其实,我真的很想再为你写点什么。只是脑里的空白就是那样的不争气。
farewell蒙珠眼波仔,一路顺风。。。farewell,ball. fight,for ball. 16 March 昨晚挂彩了。。 五人赛的第一场练习赛就挂彩了。。打惯职业赛的果然比较硬朗,一个加速,一个变向撞到一起,我严重亏了。。。。爆了眼镜还爆了眼角。。。5555555,以后绝对不能带眼镜踢球,起码要配副隐形。还算万幸,没有弄到眼睛,也就在眼角多了三根线。第一次的手术算是献出来了,在中山一院。。好在在场的有好几个都在医院做,熟人多,医生手脚也利索,半个钟头解决了问题。就是痛苦在变了只熊猫眼,还是好好修养个星期,天天做论文算了。 24 January Diary 今天好累。早上8点爬起来,然后跑去东校区开会。所谓开会,不过是说了些语无伦次的东西。那份所谓的建议书写的时候已经是一个多月前了,到现在也真的写过什么都不知道了。所以开了2个钟头会,胡言乱语了大部分的时间。也好,可以打发时间。吃过了饭,马不停蹄赶去ArrAy,桃哥突然提醒我四号线开到车陂了。于是到了万胜围,我没有下车,过了两个站之后,“大学城北”,倒,开回来了,原来那段铁轨还没开通。无奈,也就只好又坐了一遍。到了东门转上178,本来以为到了直接在华景等车就会有,把站牌横横竖竖地找了个遍,没有发现224,只好打电话给ArrAy的HR,她告诉我往回走到师大。。晕。好,顺利地上了224,遇到一个稍PP的MM,我找了她后面的位置坐下,就这样一直……到了科学城站,下车,想不到她也下来了,还幻想了一下她会不会同事,结果原来只是下来转车。沿路问了几个人,科学城的走法,终于看到了那个科学城的标志。进去了,发现迷路了,ArrAy在哪?只好又去麻烦ArrAy的那位MM,“啊?不是吧,我告诉你在小新塘站下,然后在那等穿梭巴士,我们在天河软件园,不是科学城。”倒,原来我一直都搞错了。疯掉,反正都走这么远了,干脆就多走几步到了小新塘。到了ArrAy,终于可以坐一下,可惜Crystal不在,于是又要等。下午4:30,好,终于可以走人了,还是224回来到师大,师大,一个很熟悉的地方。没有马上回来,横穿了这个校园,回味着一些事情,直到西门,那家书店,很冲动的捡了两本一直想买的书,不过也不会有什么时间看了,然后找到178一路站着回来。 很奇怪,今天坐的178有一堆奇怪的事。站我旁边也有一个不错的MM,前面两排的右边一排有一对情侣,女的靠着男的肩,熟睡着,男的拥着女的胳膊,很温馨的样子,不过,这样的平静在天河城站打破了。一个女的跑上来,喊着闹着地打电话,我无意偷听,不过声音太大。明显是被甩了,可悲,都已经说到号称要昏过去了,我回头看了看,连眼泪都看见,好假。这边终于cut了,但后坐又突然爆出一把声音,音量也很大,但我不知道她说的是什么,因为语言不通,估计是潮汕人吧。一路就这样,我站在一起上车的那位MM的旁边直到五羊新城,终于,前面的座位空了,于是MM终于有机会休息一下双腿,是时候让双手也累一下了,所以她从包里翻出一瓶东西。我没常识,所以不知道是什么,好来发现原来是抹在手上的,大概是手霜吧。“你的手很干,要不要手霜啊?”脑袋里响起一个熟悉的声音,终于被车厢的喧闹冲走,我也明白,只能是回忆了。然后,MM还掏出根棒棒糖……最后,终于到了学校,就这样,一天,结束。 17 January 悖论刚到blog友们的地盘晃悠了一圈,大家都这经历着一些甜酸苦辣。回过头来,原来我已经掉进了同一个陷阱。 纯粹理想的坚持,代价有时会让人不能承受,但这又是唯一的途径,证实天堂就在前面。理想主义通常是理想得够彻底才会有结果,但最彻底的方式却是从来不去考虑收获的。 原来我们就生活在这样的悖论里。柏拉图是对的?还是放弃这种理想才是出路?判断天堂跟地狱之间的界限,却偏偏就是成败。成可上天,败则下地。 语无伦次了。。。。提醒自己,不要碰哲学!!!对,李开复是对的,follow your heart,选你想做的,管他是不是理想主义。恐怕今晚又要胡思乱想到天亮了。。。 |
||||
|
|