我儿子正在 Raspberry Pi 上实现一个服务器,允许控制 GPIO通过网络连接的引脚。他发现了一些奇怪的行为,起初看起来像是一个错误(但请参阅下面的答案)。
首先,正在使用的操作系统是 Raspbian , Debian Linux 的一个版本.他正在使用标准系统文件来控制 GPIO 端口。
我们从一个 GPIO 引脚开始,例如引脚 17,处于非导出状态。例如,
echo "17" > /sys/class/gpio/unexport
现在,如果要求服务器打开引脚 17,它会执行以下操作:
- 打开
/sys/class/gpio/export
,向其中写入“17”,然后关闭导出文件 - 打开
/sys/class/gpio/gpio17/direction
文件进行读取,检查它是设置为输入还是输出。关闭文件。然后,如有必要,重新打开文件进行写入并将“out”写入文件,将引脚设置为输出引脚,并关闭方向文件。
此时,我们应该可以打开/sys/class/gpio/gpio17/value
进行写入,并向其中写入“1”。
但是,/sys/class/gpio/gpio17/value
文件的权限存在,但是组权限是只读的。如果我们为了等待几分之一秒而进入“ sleep ”状态,则权限会发生变化,因此组权限具有写入权限。
我原以为操作系统在正确设置值文件的权限之前,不应从写入 direction
文件返回。
为什么会这样? 这似乎是一个错误。我应该在哪里报告(更详细...)? 请参阅下面的答案。
以下是相关的代码。代码已经过一些编辑和释义,但它本质上就是正在使用的代码。 (请记住,这是一个试图学习 C++ 和 Unix 概念的 12 年级学生的代码):
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
的文件,执行失败,我们可以看到权限更改100 毫秒内的文件。
最佳答案
从内核的角度来看,已导出的每个 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/rules.d 中搜索任何包含文本“gpio”的文件,以确认您是否有这样的文件规则。顺便说一句,如果更改是通过改变引脚上的方向触发的,我会感到惊讶,更可能是通过将引脚导出到用户空间的操作。
导出设备后需要休眠一段时间的原因是,在您的进程休眠之前,systemd 服务可能没有机会运行并执行规则。
这样做的原因,而不是仅仅让内核来处理它,是为了将策略实现推送到用户空间,以便在不使内核本身过于复杂的情况下提供最大的灵 active 。
参见:systemd-udevd.service手册页和 udev手册页。
关于linux - Raspberry Pi GPIO/值文件暂时出现错误权限,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25695973/