当前位置: 首页 > 科技观察

Linux驱动实战:驱动如何向应用发送[信号]?

时间:2023-03-15 23:47:46 科技观察

别人的经历就是我们的阶梯!大家好,我是刀哥。今天给大家讲解一下技术知识点:【如何在驱动层给应用程序发送信号】。在上一篇文章中,我们讨论了:如何在应用层发送指令来控制驱动层的GPIOLinux驱动实践:如何编写[GPIO]设备驱动?。控制的方向是从应用层到驱动层:那么,如果想让程序的执行路径是自下而上,即从驱动层到应用层,应该如何实现呢?最简单最简单的方法就是通过发送信号!本文继续以一个完整的代码示例来演示如何实现这个功能。kill命令与信号使用kill命令发送信号关于Linux操作系统的信号,每个程序员都知道这个命令:使用kill工具来“杀死”一个进程:$kill-9这个命令的作用is:向指定进程发送信号9。该信号的默认功能是:停止进程。虽然在应用程序中并没有主动处理这个信号,但是操作系统默认的处理动作是终止应用程序的执行。除了发送信号9,kill命令还可以发送其他任意信号。在Linux系统中,所有的信号都是用一个整数值来表示的,你可以打开文件/usr/include/x86_64-linux-gnu/bits/signum.h(它可能在你系统的其他目录下)查看,几个常见的信号是:/*Signals.*/#defineSIGINT2/*Interrupt(ANSI).*/#defineSIGKILL9/*Kill,unblockable(POSIX).*/#defineSIGUSR110/*User-definedsignal1(POSIX).*/#defineSIGSEGV11/*Segmentationviolation(ANSI).*/#defineSIGUSR212/*User-definedsignal2(POSIX).*/......#defineSIGSYS31/*Badsystemcall.*/#defineSIGUNUSED31#define_NSIG65/*最大信号数+1(包括实时信号).*//*Thesearethehardlimitsoftthekernel.Thesevaluesshouldnotbeeddirectlyatuserlevel.*/#define__SIGRTMIN32#define__SIGRTMAX(_NSIG-1)9号信号对应SIGKILL,11号信号(SIGSEGV)是最讨厌的Segmentfault!还有一个地方要注意看一下:实时信号和非实时信号,它们的主要区别是:非实时信号:操作系统不保证应用程序能够接收到它(也就是说,信号可能会丢失);实时信号:操作系统确保应用程序能够接收到;如果我们的程序设计使用信号机制来完成一些功能,那么为了保证信号不丢失,就必须使用实时信号。从文件signum.h中可以看出,实时信号从__SIGRTMIN(值:32)开始。多线程中的信号当我们写应用程序的时候,虽然我们没有接收和处理SIGKILL信号,但是一旦别人发送这个信号,我们的程序就会被操作系统停止,这是默认的动作。那么,在应用程序中,应该可以主动声明接收并处理指定的信号。下面我们写一个最简单的例子。在一个应用程序中,可能有多个线程;当一个信号被发送到这个进程时,所有线程都可能收到它,但只有一个线程可以处理它;在这个例子中,只有一个主线程来接收和处理信号;信号注册和处理函数按照惯例,所有应用程序文件都在~/tmp/App目录中创建。//文件:tmp/App/app_handle_signal/app_handle_signal.c#include#include#include#include#include//信号处理函数staticvoidsignal_handler(intsignum,siginfo_t*info,void*context){//打印接收到的信号值printf("signal_handler:signum=%d\n",signum);}intmain(void){intcount=0;//注册信号处理函数structsigactionsa;sigemptyset(&sa.sa_mask);sa.sa_sigaction=&signal_handler;sa.sa_flags=SA_SIGINFO;sigaction(SIGUSR1,&sa,NULL);sigaction(SIGUSR2,&sa,NULL);//always循环打印信息,等待接收信号while(1){printf("app_handle_signalisrunning...count=%d\n",++count);sleep(5);}return0;}这个接收到的信号示例程序为SIGUSR1和SIGUSR2,其值为10和12。编译执行:$gccapp_handle_signal.c-oapp_handle_signal$./app_handle_signal此时应用程序开始执行,等待接收信号。在另一个终端中,使用kill命令发送信号SIGUSR1或SIGUSR2。kill发送信号,需要知道应用程序的PID,可以使用命令:ps-au|grepapp_handle_signal来查看。其中15428是进程的PID。执行发送信号SIGUSR1的命令:$kill-1015428此时,在应用程序的终端窗口中,可以看到如下打印信息:表示应用程序已经收到信号SIGUSR1!注意:我们使用kill命令来发送信号,kill也是一个独立的进程。程序的执行路径如下:在这个执行路径中,我们可以控制的部分是应用层。至于操作系统如何接收到kill操作,然后给app_handle_signal进程发送信号,我们也没有办法。并且知道。下面继续通过示例代码看看如何在驱动层主动发送信号。驱动代码示例:发送信号的功能需求在刚才的简单例子中,可以得到如下信息:信号接收者:必须定义信号处理函数,并向操作系统注册:接收哪些信号;发件人当然是司机。示例代码中,继续使用SIGUSR1信号进行测试。那么,驱动程序如何知道应用程序的PID呢?可以让应用通过oictl函数主动告诉驱动自己的PID:这里驱动中的示例代码是在上一篇文章的基础上修改的。部分内容由宏定义MY_SIGNAL_ENABLE控制,方便查看对比。以下所有操作的工作目录与上一篇相同,即:~/tmp/linux-4.15/drivers/。$cd~/tmp/linux-4.15/drivers/$mkdirmy_driver_signal$cdmy_driver_signal$touchmy_driver_signal.cmy_driver_signal.c文件内容如下(不用手动敲,文末有代码下载链接):#include#include#include#include#include//新建头文件#include#include#include#include#include//GPIO硬件相关宏定义#defineMYGPIO_HW_ENABLE//新增部分,用这个宏来控制它#defineMY_SIGNAL_ENABLE//设备名称#defineMYGPIO_NAME"mygpio"//一共4个GPIO#defineMYGPIO_NUMBER4//设备类staticstructclass*gpio_class;//用来保存thedevicestructcdevgpio_cdev[MYGPIO_NUMBER];//用来保存设备号intgpio_major=0;intgpio_minor=0;#ifdefMY_SIGNAL_ENABLE//用来保存向谁发送信号,应用程序通过ioctl设置自己的进程ID。staticintg_pid=0;#endif#ifdefMYGPIO_HW_ENABLE//硬件初始化函数,当驱动加载时(gpio_driver_init)被调用(intgpio){printk("gpio_hw_releaseiscalled:%d.\n",gpio);}//设置硬件GPIO的状态,控制GPIO时(gpio_ioctl)被查staticvoidgpio_hw_set(unsignedlonggpio_no,unsignedintval){printk("gpio_hw_setiscalled.gpio_no=%ld,val=%d.\n",gpio_no,val);}#endif#ifdefMY_SIGNAL_ENABLE//用于向应用发送信号staticvoidsend_signal(intsig_no){intret;structsiginfoinfo;structtask_struct*my_task=NULL;if(0==g_pid){//应用没有设置自己的PIDprintk("pid[%d]isnotvalid!\n",g_pid);return;}printk("sendsignal%dtopid%d\n",sig_no,g_pid);//构造一个信号结构体memset(&info,0,sizeof(structsiginfo));info.si_signo=sig_no;info.si_errno=100;info.si_code=200;//获取自己的任务信息,使用是RCU锁rcu_read_lock();my_task=pid_task(find_vpid(g_pid),PIDTYPE_PID);rcu_read_unlock();如果(my_task==NULL){printk(“getpid_taskfailed!\n");return;}//发送信号ret=send_sig_info(sig_no,&info,my_task);if(ret<0){printk("sendsignalfailed!\n");}}#endif//当application程序打开设备时调用staticintgpio_open(structnode*inode,structfile*file){printk("gpio_openiscalled.\n");return0;}#ifdefMY_SIGNAL_ENABLEstaticlonggpio_ioctl(structfile*file,unsignedintcmd,unsignedlongarg){void__user*pArg;printk("gpio_ioctliscalled.cmd=%d\n",cmd);if(100==cmd){//描述应用设置进程的PIDpArg=(void*)arg;if(!access_ok(VERIFY_READ,pArg,sizeof(int))){printk("accessfailed!\n");return-EACCES;}//将用户空间的数据复制到内核空间if(copy_from_user(&g_pid,pArg,sizeof(int))){printk("copy_from_userfailed!\n");return-EFAULT;}printk("saveg_pidsuccess:%d\n",g_pid);if(g_pid>0){//发送信号send_signal(SIGUSR1);send_signal(SIGUSR2);}}return0;}#else//应用控制GPIO时调用staticlonggpio_ioctl(structfile*file,unsignedintval,unsignedlonggpio_no){printk("gpio_ioctliscalled.\n");if(0!=val&&1!=val){printk("valisNOTvalid!\n");return0;}if(gpio_no>=MYGPIO_NUMBER){printk("dev_noisinvalid!\n");return0;}printk("setGPIO:%ldto%d.\n",gpio_no,val);#ifdefMYGPIO_HW_ENABLEgpio_hw_set(gpio_no,val);#endifreturn0;}#endifstaticconststructfile_operationsgpio_ops={.owner=THIS_MODULE,.open=gpio_open,.unlocked_ioctl=gpio_ioctl};devno;dev_tnum_dev;printk("gpio_driver_initiscalled.\n");//动态申请设备号(严格的话要检查函数返回值)alloc_chrdev_region(&num_dev,gpio_minor,MYGPIO_NUMBER,MYGPIO_NAME);//获取主设备号gpio_major=MAJOR(num_dev);printk("gpio_major=%d.\n",gpio_major);//创建设备类gpio_class=class_create(THIS_MODULE,MYGPIO_NAME);//创建设备节点for(i=0;i#include#include#include#include#include#include#defineMY_GPIO_NUMBER4chargpio_name[MY_GPIO_NUMBER][16]={"/dev/mygpio0","/dev/mygpio1","/dev/mygpio2","/dev/mygpio3"};//信号处理函数staticvoidsignal_handler(intsignum,siginfo_t*info,void*context){//打印接收到的信号值printf("signal_handler:signum=%d\n",signum);printf("signo=%d,code=%d,errno=%d\n",info->si_signo,info->si_code,info->si_errno);}intmain(intargc,char*argv[]){intfd,count=0;intpid=getpid();//打开GPIOif((fd=open("/dev/mygpio0",O_RDWR|O_NDELAY))<0){printf("opendevfailed!\n");return-1;}printf("opendevsuccess!\n");//注册信号处理函数structsigactionsa;sigemptyset(&sa.sa_mask);sa.sa_sigaction=&signal_handler;sa.sa_flags=SA_SIGINFO;sigaction(SIGUSR1,&sa,NULL);sigaction(SIGUSR2,&sa,NULL);//setPIDprintf("callioctl.pid=%d\n",pid);ioctl(fd,100,&pid);//休眠1秒,等待接收信号sleep(1);//关闭设备close(fd);}可以看出应用程序主要做了两件事:(1)首先通过函数sigaction向操作系统注册信号SIGUSR1()和SIGUSR2,两者的信号处理函数是一样的:signal_handler()除了sigaction函数,应用程序还可以使用signal函数来注册信号处理函数;(2)然后通过ioctl(fd,100,&pid);给驱动设置自己的PID。编译应用程序:$gccmysignal.c-omysignal执行应用程序:$sudo./mysignal根据刚才驱动程序的代码,当驱动程序收到设置PID的命令时,会立即发送两个信号:首先看一下dmesg驱动的打印信息:可以看到驱动把这两个信号(10和12)发给了应用程序(PID=6259)。应用程序的输出信息如下:可以看到应用程序接收到信号10和12,并正确打印出了信号中携带的一些信息!本文转载自微信♂「IOT物联网小镇」