2014-09-06 79 views
0

我儿子在Raspberry Pi上实现一个服务器,允许通过网络连接控制GPIO引脚。他发现了一些奇怪的行为,最初看起来像一个错误(但请参阅下面的答案)。Raspberry Pi GPIO/value文件暂时出现错误权限

首先,使用的操作系统是RaspbianDebian Linux的一个版本。他正在使用标准系统文件来控制GPIO端口。

我们从一个GPIO引脚开始,例如,引脚17,处于非导出状态。例如,现在

echo "17" > /sys/class/gpio/unexport 

,如果服务器被要求打开销17,它执行以下操作:

  1. 打开/sys/class/gpio/export,写道:“17”的微博,关闭导出文件
  2. 打开/sys/class/gpio/gpio17/direction文件进行读取,检查它是否设置为输入或输出。关闭文件。然后,如有必要,重新打开要写入的文件并将“out”写入文件,将该引脚设置为输出引脚,然后关闭方向文件。

此时,我们应该可以打开/sys/class/gpio/gpio17/value进行写入,并写入“1”。

但是,存在/sys/class/gpio/gpio17/value文件的权限,但组权限是只读的。如果我们为了等待几分之一秒而进入“睡眠”状态,权限会发生变化,因此组权限具有写入权限。

我原以为操作系统不应该从写入到direction文件返回,直到它正确设置了值文件的权限。

这是怎么发生的? 这看起来像一个错误。我应该在哪里报告(更详细的...)?请参阅下面的答案。

以下是代码的相关位。代码已经被编辑和解释了一下,但它基本上是被使用的。 (请记住,这是一个12年级的学生努力学习C++和Unix概念的代码):

class GpioFileOut 
{ 
    private: 
     const string m_fName; 
     fstream m_fs; 

    public: 
     GpioFileOut(const string& sName) 
     : m_fName(("/sys/class/gpio/" + sName).c_str()) 
     { 
      m_fs.open(m_fName.c_str()); 
      if (m_fs.fail()) 
      { 
       cout<<"ERROR: attempted to open " << m_fName << " but failed" << endl << endl; 
      } 
      else 
      { 
       cout << m_fName << " opened" << endl; 
      } 
     } 

     ~GpioFileOut() 
     { 
      m_fs.close(); 
      cout << m_fName << " closed" << endl << endl; 
     } 

     void reOpen() 
     { 
      m_fs.close(); 
      m_fs.open(m_fName); 
      if (m_fs.fail()) 
      { 
       cout<<"ERROR: attempted to re-open " << m_fName << " but failed" << endl << endl; 
      } 
      else 
      { 
       cout << m_fName << " re-opened" << endl; 
      } 
     } 

     GpioFileOut& operator<<(const string &s) 
     { 
      m_fs << s << endl; 
      cout << s << " sent to " << m_fName << endl; 
      return *this; 
     } 

     GpioFileOut& operator<<(int n) 
     { 
      return *this << to_string(n); //ostringstream 
     } 

     bool fail() 
     { 
      return m_fs.fail(); 
     } 
}; 

class GpioFileIn 
{ 
    private: 
     ifstream m_fs; 
     string m_fName; 

    public: 
     GpioFileIn(const string& sName) 
     : m_fs(("/sys/class/gpio/" + sName).c_str()) 
     , m_fName(("/sys/class/gpio/" + sName).c_str()) 
     { 
      if (m_fs <= 0 || m_fs.fail()) 
      { 
       cout<<"ERROR: attempted to open " << m_fName << " but failed" << endl; 
      } 
      else 
      { 
       cout << m_fName << " opened" << endl; 
      } 
     } 

     ~GpioFileIn() 
     { 
      m_fs.close(); 
      cout << m_fName << " closed" << endl << endl; 
     } 

     void reOpen() 
     { 
      m_fs.close(); 
      m_fs.open(m_fName); 
      if (m_fs <= 0 || m_fs.fail()) 
      { 
       cout<<"ERROR: attempted to re-open " << m_fName << " but failed" << endl; 
      } 
      else 
      { 
       cout << m_fName << " re-opened" << endl; 
      } 
     } 

     GpioFileIn& operator>>(string &s) 
     { 
      m_fs >> s; 
      cout << s << " read from " << m_fName << endl; 
      return *this; 
     } 
     bool fail() 
     { 
      return m_fs.fail(); 
     } 
}; 

class Gpio 
{ 
    public: 
     static const bool OUT = true; 
     static const bool IN = false; 
     static const bool ON = true; 
     static const bool OFF = false; 

     static bool setPinDirection(const int pinId, const bool direction) 
     { 
      GpioFileOut dirFOut(("gpio" + to_string(pinId) + "/direction").c_str()); 
      if (dirFOut.fail()) 
      { 
       if (!openPin(pinId)) 
       { 
        cout << "ERROR! Pin direction not set: Failed to export pin" << endl; 
        return false; 
       } 
       dirFOut.reOpen(); 
      } 
      dirFOut << (direction == OUT ? "out" : "in"); 
     } 


     static bool setPinValue(const int pinId, const bool pinValue) 
     { 
      string s; 
      { 
       GpioFileIn dirFIn(("gpio" + to_string(pinId) + "/direction").c_str()); 
       if (dirFIn.fail()) 
       { 
        if (!openPin(pinId)) 
        { 
         cout << "ERROR! Pin not set: Failed to export pin"<<endl; 
         return false; 
        } 
        dirFIn.reOpen(); 
       } 
       dirFIn >> s; 
      } 

      if (strncmp(s.c_str(), "out", 3) == 0) 
      { 
       struct stat _stat; 
       int nTries = 0; 
       string fname("/sys/class/gpio/gpio"+to_string(pinId)+"/value"); 

       for(;;) 
       { 
        if (stat(fname.c_str(), &_stat) == 0) 
        { 
         cout << _stat.st_mode << endl; 
         if (_stat.st_mode & 020) 
          break; 
        } 
        else 
        { 
         cout << "stat failed. (Did the pin get exported successfully?)" << endl; 
        } 

        cout << "sleeping until value file appears with correct permissions." << endl; 
        if (++nTries > 10) 
        { 
         cout << "giving up!"; 
         return false; 
        } 
        usleep(100*1000); 
       }; 
       GpioFileOut(("gpio" + to_string(pinId) + "/value").c_str()) << pinValue; 
       return true; 
      } 
      return false; 
     } 

     static bool openPin(const int pinId) 
     { 
      GpioFileOut fOut("export"); 
      if (fOut.fail()) 
       return false; 
      fOut << to_string(pinId); 
      return true; 
     } 
} 

int main() 
{ 
    Gpio::openPin(17); 
    Gpio::setPinDirection(17, Gpio::OUT) 
    Gpio::setPinValue(17, Gpio::ON); 
} 

关键的一点是:没有for(;;)环路stat的文件,执行失败,我们可以在100ms内看到文件权限的变化。

+1

您应该最好尝试将您的问题发布到http://raspberrypi.stackexchange.com/ – mpromonet 2014-09-06 13:43:05

+0

谢谢。会做。 – 2014-09-06 16:41:11

+0

感谢您的编辑 - 现在我知道如何制作一个列表。 – 2014-09-06 16:47:09

回答

2

从内核角度来看,已导出的每个GPIO引脚的'值'文件都使用模式0644和所有权root:root来创建。当你写入'方向'文件时,内核不会做任何改变。

您所描述的行为是由于systemd udev服务的操作。该服务监听来自内核的有关设备状态变化的事件,并相应地应用规则。

当我在我自己的Pi上进行测试时,我没有遇到您描述的行为 -/sys中的gpio文件全部由root:root拥有,且模式为0644,并且不管方向如何都不会改变。但是我正在运行Pidora,而且我的系统中找不到与此有关的任何udev规则。我假设Raspbian(或者您添加到系统中的一些软件包)添加了这样的规则。

我确实发现this thread其中提到了一些建议的规则。特别是这条规则这将有效果你描述:

SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'chown -R root:gpio /sys/class/gpio; chmod -R 770 /sys/class/gpio; chown -R root:gpio /sys/devices/virtual/gpio; chmod -R 770 /sys/devices/virtual/gpio'" 

可以在/lib/udev/rules.d,/usr/lib/udev/rules.d和/ etc/udev的/规则搜索。 d包含文本'gpio'的任何文件以确认您是否拥有此类规则。顺便说一句,如果更改是由引脚上的方向改变引起的,更可能是通过将引脚导出到用户空间的行为,我会感到惊讶。

导出设备后需要休眠一段时间的原因是,在您的进程休眠之前,systemd服务可能无法运行并执行规则。

这样做的原因不在于让内核处理它,而是将策略实现推送到用户空间,以便提供最大的灵活性,而不会过度复杂化内核本身。

请参阅:systemd-udevd.service手册页和udev手册页。

+0

“如果更改是由引脚上的方向改变引起的,更可能是将引脚导出到用户空间,我会感到惊讶。” - 同意。 “方向”文件的行为方式相同,所以我相信你是正确的。 – 2014-09-10 17:26:27

+0

这听起来很棒。我会让我的儿子今晚在家里查看详情,然后回来。但有一个问题:你的设置(root拥有的文件:root,perms = 0644)意味着只有root可以改变方向或者改变值。这似乎是一个奇怪的设置,但我猜想,如果你打算使用gpio的话,你应该加入一些udev规则来改变设置 - 看起来相当危险,要求任何想要使用gpio的进程运行根。 – 2014-09-10 17:32:24

+0

@ DavidI.McIntosh我的设置不包含任何udev GPIO规则(我不确定这是否是pidora中的疏忽,某些软件包我没有安装,或者您应该手动添加它们),所以我有默认设置内核权限,这是限制性的。正如你所说的想法是,如果你想使用来自用户空间的GPIO,你应该添加这些规则。 – harmic 2014-09-10 23:35:48