博文

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

C编程控制PC蜂鸣器

在《I/O映射之I/O端口》一文中,我们实现了在Linux内核驱动里实现IO端口操作8254定时计数器芯片里的寄存器来实现PC主机蜂鸣器的控制,那么在C语言里是不是一样也能用io控制函数来操作呢?当然是可以的,只不过作为应用程序,其需要使用iopl或ioperm函数先获得IO操作的权限,其函数原型如下: 1.iopl - change I/O privilege level #include <sys/io.h> int iopl(int level); 2.ioperm - set port input/output permissions #include <unistd.h> /* for libc5 */ #include <sys/io.h> /* for glibc */ int ioperm(unsigned long from, unsigned long num, int turn_on); 其中,iopl修改程序运行时操作io的权限级别,默认是0,最大是3;而ioperm是设置从from开始的num个io端口turn_on状态。(详细看man) 有了《I/O映射之I/O端口》一文和上面的内容学习,接下来用C来实现就很容易了,其中关于inb等函数头可使用man inb命令查阅,下面上代码吧: #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 val) { unsigned int count = 0; if (val > 20 && val < 32767) count = PIT_TICK_RATE / val; iopl(3); if (count) { outb_p(0xB6, 0x43);

Shell命令控制蜂鸣器发声

在《I/O映射之I/O端口》一文中我们实现了在Linux驱动里通过IO端口操作PC机蜂鸣器发声,那么在Shell脚本里能否实现呢? 首先,我们先确认下自己的Ubuntu13.10里有没有安装beep这个命令?如果没有则使用如下命令进行安装: sudo apt-get install beep 安装好后,先看下其帮助信息吧,主要是下面这句: beep [–verbose –debug] [-e device –device device] [-f N] [-l N] [-r N] [-d N] [-D N] [-s] [-c] 其中, –verbose或–debug选项开启调试信息输出功能; -e device或–device device选项设定event设备,未指定则尝试/dev/tty0和/dev/vc/0两设备; -f N表示beep的频率为N Hz,0<N<20000; -l N表示beep多久,为N毫秒; -r N表示beep重复次数,默认为1; -d N和-D N表示两次重复间的延时为N毫秒,其中d则表示最后一次重复不发生该延时,而D则表示每次都有该延时; -s和-c表示beep时需从stdin读取,s表示每读一行,c表示每读一字符。 好了,下面还是先测试下有没有声音出来吧: beep -f 300.7 -r 2 -d 100 -l 400 我们用man beep里的例子进行测试,但声音没有出来?查了下,发现驱动没有加载: lsmod grep pcspkr 上面这条命令返回空,确认Linux kernel的配置里CONFIG_INPUT_PCSPKR=m,那就更肯定是没加载了,继续查,最后发现在/etc/modprobe.d/blacklist.conf文件里居然有如下这句: blacklist pcspkr 看来是每次启动时都被黑名单过滤掉了,我们手动加载下吧: sudo modprobe pcspkr 这时再执行上面的beep命令就有声音出来了,如果你需要在Shell脚本里使用beep命令,记得用上/usr/bin/beep这个绝对路径,别只用命令,还有如果该Shell脚本要每次启动都能成功执行,那么上面的blacklist一句记得注释掉。 参考网址: http://b

I/O映射之I/O端口

图片
对于外设,操作系统会采用端口映射和内存映射两种方式来对其进行控制,其中端口映射就是操作系统规定好一段地址给指定换上设,其与外设的寄存器按顺序一一对应上。 在Linux内核源码include/asm-generic/io.h头文件里有如下我们对I/O port进行操作的函数: static inline u8 inb(unsigned long addr) { return readb(addr + PCI_IOBASE); } static inline u16 inw(unsigned long addr) { return readw(addr + PCI_IOBASE); } static inline u32 inl(unsigned long addr) { return readl(addr + PCI_IOBASE); } static inline void outb(u8 b, unsigned long addr) { writeb(b, addr + PCI_IOBASE); } static inline void outw(u16 b, unsigned long addr) { writew(b, addr + PCI_IOBASE); } static inline void outl(u32 b, unsigned long addr) { writel(b, addr + PCI_IOBASE); } #define inb_p(addr) inb(addr) #define inw_p(addr) inw(addr) #define inl_p(addr) inl(addr) #define outb_p(x, addr) outb((x), (addr)) #define outw_p(x, addr) outw((x), (addr)) #define outl_p(x, addr) outl((x), (addr)) 分别对应对端口进行读取或写入字节、字、双字大小数据的操作。 该头文件中还有其他的操作函数及宏定义,请自行深入了解。 我们可通过读取/proc/ioports文件来了解Linux里I/O端口的映射

定时中断键盘灯闪烁

在学习完《趣味定时器》后,是不是只在dmesg里看到效果很没劲,这次来点更实际的,我们让键盘灯闪起来。 对于定时器,就不多说了,接下来了解下键盘的基本内容。 键盘在Linux里是属于TTY(TeleTYpe)设备,既然我们要用到键盘,那先找其通用驱动代码,在Linux Kernel源码drivers/tty/vt/keyboard.c文件,我们找到需要的LED灯控制函数: void setledstate(struct kbd_struct  kbd, unsigned int led) ; 该函数被在同一文件的vt_do_kdskled()函数调用,而这函数又被drivers/tty/vt/vt_ioctl.c文件的vt_ioctl()函数调用,这函数是drivers/tty/vt/vt.c里 struct tty_operations con_ops的ioctl成员,在同一文件vty_init()函数里有如下语句: tty_set_operations(console_driver, &con_ops); 将 console_driver->ops赋值为&con_ops,而console_driver是如下类型: struct tty_driver *console_driver; 该结构体在include/linux/tty_driver.h定义,其被include/linux/tty.h文件中的struct tty_struct结构体包含,其又被同文件的struct tty_port结构体包含。 而我们在 drivers/tty/vt/vt_ioctl.c文件中有看到vt_disallocate_all()函数里有tty_port_destroy(&vc[i]->port); 操作,那么找到vc就可以关联起来,在include/linux/console_struct.h文件中有vc的类型struct vc_data结构体定义,而在drivers/tty/vt/selection.c文件中的set_selection()函数里有struct vc_data *vc = vc_cons[fg_console].d; 赋值操作,那么往下找vc_cons,其在drivers/tty/vt/vt.c文件中定义为struct v

内核线程同步之信号量、互斥量、自旋锁、原子量

本次主要是学习线程同步时保护数据的锁机制,对于多个线程同时对一个变量进行赋值操作时,会造成该变量的赋值不确定,出现了竞争状态,这时需要有个锁来保护下,即加个锁,同一时间只能有一个线程对其操作,当锁释放了,另一线程才能对其操作,而处在加锁和解锁之间的区域叫做临界区,对临界区的操作要保证其原子性,即一次性搞定,不能被中断,而内核也提供了原子量,用于轻量级操作。 接下来说说内核里相关的内容吧: 1.原子量 1).结构体 在include/linux/types.h文件中有如下结构体(分别对应32位和64位系统): typedef struct { int counter; } atomic_t; #ifdef CONFIG_64BIT typedef struct { long counter; } atomic64_t; #endif 2).整型原子操作 在include/asm-generic/atomic.h文件中有如下操作宏和函数定义: a.创建及初始化 #define ATOMIC_INIT(i) { (i) } #define atomic_set(v, i) (((v)->counter) = (i)) b.读取原子量 #define atomic_read(v) (*(volatile int *)&(v)->counter) c.原子量常用运算(加、减、自加一、自减一) static inline void atomic_add(int i, atomic_t *v) ; static inline void atomic_sub(int i, atomic_t *v) ; static inline void atomic_inc(atomic_t *v) ; static inline void atomic_dec(atomic_t *v) ; d.原子量运算并检测运算后是否等于0 #define atomic_sub_and_test(i, v) (atomic_sub_return((i), (v)) == 0) #define atomic_dec_and_test(v) (atomic_dec_return(v) == 0) #define atomic_i

内核线程同步之wait_queue

在《内核线程同步之completion》一文中说到completion完成量也是基于wait_queue等待队列机制实现(这些机制不仅仅用于内核线程的同步,也可用于其他相关场景,这里用线程演示是其比较方便感受到效果),那么接下来就来了解下这一机制的相关内容,其在Linux Kernel源码include/linux/wait.h文件中有如下内容: typedef struct __wait_queue wait_queue_t; struct __wait_queue { unsigned int flags; #define WQ_FLAG_EXCLUSIVE 0x01 void *private; wait_queue_func_t func; struct list_head task_list; }; struct __wait_queue_head { spinlock_t lock; struct list_head task_list; }; typedef struct __wait_queue_head wait_queue_head_t; 上面包含了等待队列和等待队列头部类型的结构体声明。其对应有如下静态初始化宏: #define DECLARE_WAITQUEUE(name, tsk) \ wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk) #define DECLARE_WAIT_QUEUE_HEAD(name) \ wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name) 还有如下动态初始化宏和函数: #define init_waitqueue_head(q) \ do { \ static struct lock_class_key __key; \ \ __init_waitqueue_head((q), #q, &__key); \

内核线程同步之completion

Completion,完成量,用于多线程间同步,即线程A要往下执行需要等待线程B执行到指定代码后才继续执行,这时就可以使用该机制,用于一个线程告诉另一个线程指定工作已完成。 在Linux Kernel源码include/linux/completion.h文件里有相关的结构体和接口函数的定义和声明,从结构体可以看到其是基于等待队列机制实现的,该机制后期再了解,下面是completion相关内容: struct completion { unsigned int done; wait_queue_head_t wait; }; static inline void init_completion(struct completion *x) ; static inline void reinit_completion(struct completion *x); extern void wait_for_completion(struct completion *); extern int wait_for_completion_interruptible(struct completion *x); extern int wait_for_completion_killable(struct completion *x); extern unsigned long wait_for_completion_timeout(struct completion *x, unsigned long timeout); extern long wait_for_completion_interruptible_timeout( struct completion *x, unsigned long timeout); extern long wait_for_completion_killable_timeout( struct completion *x, unsigned long timeout); extern bool try_wait_for_completion(struct completion *x); extern bool completion_done(struct c

Linux下C++编程使用动态链接库

在《 Linux 下 C 编程使用动态链接库》一文中已了解到了 C 语言里如何使用共享库 SO 了,但在 C++ 里可全是类,该如何实现呢? C 语言中的操作只能导出全局函数,并不能导出类的方法,故而需要设计相关的全局函数来封装一层。这里用到了“类工厂函数”的设计模式,定义一个抽象类(含有纯虚函数)的头文件,然后在 SO 源文件和使用的源文件里均包含该头文件,在 SO 里继承并实现抽象类里的纯虚函数。 具体看下面的实例,头文件里创建多边形类(作为接口),而在 SO 继承它并实现三角形类,并且设计了全局的创建和销毁类对象的函数: polygon.hpp (纯虚父类,多边形类): #ifndef POLYGON_HPP #define POLYGON_HPP class polygon { protected: double _side_length; public: polygon() : _side_length(0) {} virtual ~polygon() {} void set_side_length(double side_length) { _side_length = side_length; } virtual double area() const = 0; }; typedef polygon* create_t(); typedef void destroy_t(polygon*); #endif triangle.cpp (三角形类,继承多边形类,实现其纯虚函数): #include "polygon.hpp" #include <math.h> class triangle : public polygon { public: virtual double area() const { return _side_length * _side_length * sqrt(3) / 2; } }; extern "C" polygon* create() { r

C编程实现键盘LED灯闪烁

在《Shell脚本实现键盘LED灯闪烁》一文中,我们已感受到了控制的乐趣,一步步向硬件逼近,这次我们在Linux下使用C语言进行系统调用来实现该功能。这里面会涉及到应用层定时器和ioctl系统调用来控制键盘LED灯状态。 关于应用层定时器需要涉及到信号机制,其包含有alarm闹钟和timer定时器两种,其与信号机制分别说明如下(这两种均是自动循环的,即不需要处理函数里再设置一遍定时器超时设置): 1.alarm 运行man alarm命令后有关于该函数的使用说明,其函数原型如下: #include <unistd.h> unsigned int alarm(unsigned int seconds); 当设定的时间到了后会发出SIGALRM信号,需要对应的信号处理函数配套处理。 2.timer 运行man setitimer命令后有相关说明,本次会用到的函数原型如下: #include <sys/timer.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); 其中which是设置的定时器类型,分别对应如下: ITIMER_REAL:实时定时器,发SIGALRM信号; ITIMER_VIRTUAL:应用进程执行时间定时器,发SIGVTALRM信号; ITIMER_PROF:应用进程执行和内核交互时间定时器,发SIGPROF信号。 从上面可以看出,同一时间只能有3个定时器存在,分别对应3种类型。那么上面的信号要如何处理呢?执行man signal命令有相关的函数原型如下: #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 这signal函数除了与定时器配套外,还可以捕获其他信号,执行man 7 signal命令可以查看到。 好了,关于定时器部分就说到这。 而关于键盘LED灯控制,我们需要用到TTY设备的ioctl系统调用函数,该函数是标准的字符设备接口(可以不实现),其接口原型(执

内核线程

有时候我们需要有个程序来监听和处理某些指定事件,此时经常会做个服务,让其在后台执行,这在应用程序这样的用户态很经常用到,而在Linux Kernel里也会有类似的经历,此时同样可以使用线程来实现,不过它叫内核线程,只能在内核中由其他的线程来创建,而所有的内核线程由kthreadd创建,故而使用ps -ef命令看到所有被[]括起来的内核线程(守护进程)对应的PPID均为2。内核线程与普通用户态线程除了内核线程没有独立地址空间(其mm成员指向NULL)外,其他的可被调度和被抢占均支持。 我们还是以实践为主,下面是这次会使用到跟内核线程相关的API函数和宏,在include/linux/kthread.h文件中均有定义: 1.kthread_create(),创建内核线程 #define kthread_create(threadfn, data, namefmt, arg...) \ kthread_create_on_node(threadfn, data, -1, namefmt, ##arg) 2.kthread_run(),创建并运行内核线程 #define kthread_run(threadfn, data, namefmt, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \ wake_up_process(__k); \ __k; \ }) 看到与kthread_create()的区别了吧,多了个wake_up_process()操作。 3.kthread_stop(),停止指定内核线程 int kthread_stop(struct task_struct *k); 4.kthread_should_stop(),线程该停止吗? bool kthread_should_stop(void); 5.kthread_bind(),绑定创建好的线程在执行CPU核心上运行 void kthread_bind(struct

使用SSH KEY

图片
在使用 git 时,如果使用 gitolite 架设的服务器时,需要用到 ssh key 来作验证,同时也是方便每次操作 git 库时免去输密码的麻烦。同样情况,我们在常用的服务器与客户端之间使用 SSH 访问时,也可以使用 ssh key 来验证登录身份,每次只需连接即可。 在 SSH 里提供了两种级别的安全验证,其中一种就是最常用的帐号密码登录方式,这种级别在内网倒没什么问题,如果是在外网,建议使用基于“公钥加密”机制的安全验证了,其采用了非对称加密方式,将公钥上传到服务器,每次从服务器获取数据时,服务器先用公钥将数据加密,然后客户端收到加密后的数据 包再使用相应的私钥解密还原数据,这样就可以保证密文在网络传输过程中的安全,即使有人劫获了密文,没有对应的密钥也是还原不了数据。 那么接下来还是看看如何使用吧! 1. 生成钥匙对 xinu@slam:~$ ssh-keygen -t rsa -C “ guochongxin@qq.com ” 使用上述命令生成钥匙对,其中 -t 表示钥匙对采用的加密类型为后面设置的 rsa , -C 是注释,说明这个 KEY 的用途等,这里使用邮箱主要是方便管理,说明这个 KEY 是谁的。 运行该命令后有如下内容输出: Generating public/private rsa key pair. Enter file in which to save the key (/home/xinu/.ssh/id_rsa): 这里按“回车”键确认生成的钥匙对保存在默认地址,接下来会提示如下: Enter passphrase (empty for no passphrase): 这里要求输入密码,如果回车表示密码为空,请按实际处理,输入密码后会提示再次输入确认: Enter same passphrase again: 输入并“回车”后会生成 KEY ,截图如下: 上述显示了该钥匙的私钥为 /home/xinu/.ssh/id_rsa ,公钥为 /home/xinu/.ssh/id_rsa.pub ,还有指纹及随机艺术图,该钥匙对文件以 ASCII 码形式保存,可随意打开查阅。 2. 多钥匙配置 当我们再次生成一把钥匙或从朋友那拿到一个服务器登录的私钥时,我们如何使用这