CONFIG_PREEMPT_RCU=y # CONFIG_PREEMPT_NONE is not set # CONFIG_PREEMPT_VOLUNTARY is not set CONFIG_PREEMPT=y CONFIG_PREEMPT_COUNT=y CONFIG_DEBUG_PREEMPT=y # CONFIG_PREEMPT_TRACER is not set
staticint major_num; staticint device_open_count = 0; staticchar* msg_buffer = "this is a demo"; staticchar* msg_ptr;
staticssize_tdevice_read(struct file *flip, char __user *buffer, size_t len, loff_t *offset){ int bytes_read = 0; mdelay(12000); while(len) { //If we’re at the end, loop back to the beginning if (*msg_ptr == 0) { msg_ptr = msg_buffer; } //buffer is in user data, not kernel, so you can’t just reference //with a pointer. The function put_user handles this for us */ put_user(*(msg_ptr++), buffer++); len--; bytes_read++; } return bytes_read; }
staticssize_tdevice_write(struct file *flip, constchar __user *buffer, size_t len, loff_t *offset){ printk(KERN_ALERT "This operation is not supported.\n"); return -EINVAL; }
//Called when a process opens our device staticintdevice_open(struct inode *inode, struct file *file){ //if device is open, return busy if (device_open_count) { return -EBUSY; } device_open_count++; try_module_get(THIS_MODULE); return0; }
//called when a process closes our device staticintdevice_release(struct inode *inode, struct file *file){ //decrement the open counter and usage count. Without this, the module would not unload device_open_count--; module_put(THIS_MODULE); return0; }
staticint __init demo_init(void){ msg_ptr = msg_buffer; //try to register character device major_num = register_chrdev(0, "demo", &file_ops); if (major_num < 0) { printk(KERN_ALERT "Could not register device: %d\n", major_num); return major_num; } else { printk(KERN_INFO "demo module loaded with device major number %d\n", major_num); return0; } }
staticvoid __exit demo_exit(void){ //Remember — we have to clean up after ourselves. Unregister the character device. */ unregister_chrdev(major_num, DEVICE_NAME); printk(KERN_INFO "Goodbye, World!\n"); }
# tail -n 4 /var/log/messages ...... Apr 2 23:12:46 devbuild kernel: demo module loaded with device major number 249
# mknod /dev/demo c 249 0
运行用户态程序:
1 2 3 4 5 6 7 8 9
# uname -a Linux devbuild 3.19.8.hyg.preemption.none+ #4 SMP Thu Apr 2 01:17:08 CST 2020 x86_64 x86_64 x86_64 GNU/Linux
# make test taskset --cpu-list 1 ./a.out T1 start at 1585840558 T2 start at 1585840558 T1 stop at 1585840571. elapse: 13 seconds T2 stop at 1585840571. elapse: 13 seconds. buf=[19:"this is a demothis "]
# uname -a Linux devbuild 3.19.8.hyg.preemption.preemptible+ #5 SMP PREEMPT Thu Apr 2 13:51:19 CST 2020 x86_64 x86_64 x86_64 GNU/Linux
# make test taskset --cpu-list 1 ./a.out T1 start at 1585841585 T2 start at 1585841585 T1 stop at 1585841588. elapse: 3 seconds T2 stop at 1585841598. elapse: 13 seconds. buf=[19:"this is a demothis "]
# uname -a Linux devbuild 3.19.8.hyg.preemption.voluntary+ #7 SMP Sat Apr 4 08:16:39 CST 2020 x86_64 x86_64 x86_64 GNU/Linux
# make test taskset --cpu-list 1 ./a.out T1 start at 1585968037 T2 start at 1585968037 T1 stop at 1585968050. elapse: 13 seconds T2 stop at 1585968050. elapse: 13 seconds. buf=[19:"is a demothis is a "]
staticssize_tdevice_read(struct file *flip, char __user *buffer, size_t len, loff_t *offset){ int bytes_read = 0; mdelay(6000); might_sleep(); mdelay(6000); while(len) { //If we’re at the end, loop back to the beginning if (*msg_ptr == 0) { msg_ptr = msg_buffer; } //buffer is in user data, not kernel, so you can’t just reference //with a pointer. The function put_user handles this for us */ put_user(*(msg_ptr++), buffer++); len--; bytes_read++; } return bytes_read; }
# make test taskset --cpu-list 1 ./a.out T1 start at 1585968353 T2 start at 1585968353 T1 stop at 1585968360. elapse: 7 seconds T2 stop at 1585968366. elapse: 13 seconds. buf=[19:"this is a demothis "]
如何保证这个共识呢?比如我写个函数里面会sleep,我可能在注释中写上”this function may sleep”,但这也不能保证别人不在临界区里调用它啊。函数might_sleep()就是用来解决这个问题的:它会检查当前上下文是不是在临界区里,假如在临界区,就会panic。假如我写一个会sleep的函数,我就在函数开头调用一下might_sleep(),别人在临界区调用我的函数,也就panic了。当然这是在debug(CONFIG_DEBUG_ATOMIC_SLEEP被定义)模式下的行为;假如CONFIG_DEBUG_ATOMIC_SLEEP没被定义,might_sleep()的作用就相当于一个annotation。所以,现在的内核代码中,可能sleep的函数,大都有对might_sleep()的调用。
#ifdef CONFIG_DEBUG_ATOMIC_SLEEP # define might_sleep() \ do { __might_sleep(__FILE__, __LINE__, 0); } while (0) #else # define might_sleep() do { } while (0) #endif
void __might_sleep(constchar *file, int line, int preempt_offset) { /* * Blocking primitives will set (and therefore destroy) current->state, * since we will exit with TASK_RUNNING make sure we enter with it, * otherwise we will destroy state. */ WARN_ONCE(current->state != TASK_RUNNING && current->task_state_change, "do not call blocking ops when !TASK_RUNNING; " "state=%lx set at [<%p>] %pS\n", current->state, (void *)current->task_state_change, (void *)current->task_state_change);
___might_sleep(file, line, preempt_offset); }
void ___might_sleep(constchar *file, int line, int preempt_offset) { staticunsignedlong prev_jiffy; /* ratelimiting */
rcu_sleep_check(); /* WARN_ON_ONCE() by default, no rate limit reqd. */ if ((preempt_count_equals(preempt_offset) && !irqs_disabled() && !is_idle_task(current)) || system_state != SYSTEM_RUNNING || oops_in_progress) return; if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) return; prev_jiffy = jiffies;
printk(KERN_ERR "BUG: sleeping function called from invalid context at %s:%d\n", file, line); printk(KERN_ERR "in_atomic(): %d, irqs_disabled(): %d, pid: %d, name: %s\n", in_atomic(), irqs_disabled(), current->pid, current->comm);
debug_show_held_locks(current); if (irqs_disabled()) print_irqtrace_events(current); #ifdef CONFIG_DEBUG_PREEMPT if (!preempt_count_equals(preempt_offset)) { pr_err("Preemption disabled at:"); print_ip_sym(current->preempt_disable_ip); pr_cont("\n"); } #endif dump_stack(); }