芯片運行在極限的邊緣,怪異問題集(一)創建消息隊列,錯誤碼24

現象描述:板子中原本運行有A、B、C三個進程。其中A進程優先級比較高。現在將A的優先級恢復到普通優先級後,再添加D進程。發現B、C會隨機啟動不起來。將A優先級提高或刪掉D後,又全部可以正常運行。

進一步分析log,返現B、C起不來是因為mq_open失敗,返回24,即EMFILE。

解決方案:步驟1:按照常規解決問題的方法,先查EMFILE的原因,大概意思是進程打開文件數量超過系統限制(我的開發板是1024個),但是我的進程壓根沒有打開多少文件。所以,這應該是不可能的。增大文件數量的限制,果然沒有效果。

步驟2:分析mq_open系統調用源碼:sys_mq_open調用do_create,do_create調用vfs_create,vfs_create調用inode_operations的create,即mqueue_create

static const struct inode_operations mqueue_dir_inode_operations = {
\t.lookup = simple_lookup,
\t.create = mqueue_create,
\t.unlink = mqueue_unlink,
};

mqueue_create調用了我們的主角mqueue_get_inode,重點分析該函數:

static struct inode *mqueue_get_inode(struct super_block *sb,
\t\tstruct ipc_namespace *ipc_ns, umode_t mode,
\t\tstruct mq_attr *attr)
{
\tstruct user_struct *u = current_user();//獲取當前用戶
\tstruct inode *inode;
\tint ret = -ENOMEM;
\tinode = new_inode(sb);//為queue創建一個文件節點
\tif (!inode)
\t\tgoto err;
\tinode->i_ino = get_next_ino();
\tinode->i_mode = mode;
\tinode->i_uid = current_fsuid();
\tinode->i_gid = current_fsgid();

\tinode->i_mtime = inode->i_ctime = inode->i_atime = CURRENT_TIME;
\tif (S_ISREG(mode)) {
\t\tstruct mqueue_inode_info *info;
\t\tunsigned long mq_bytes, mq_treesize;
\t\tinode->i_fop = &mqueue_file_operations;
\t\tinode->i_size = FILENT_SIZE;
\t\t/* mqueue specific info */
\t\tinfo = MQUEUE_I(inode);
\t\tspin_lock_init(&info->lock);
\t\tinit_waitqueue_head(&info->wait_q);
\t\tINIT_LIST_HEAD(&info->e_wait_q[0].list);
\t\tINIT_LIST_HEAD(&info->e_wait_q[1].list);
\t\tinfo->notify_owner = NULL;
\t\tinfo->notify_user_ns = NULL;
\t\tinfo->qsize = 0;
\t\tinfo->user = NULL;\t/* set when all is ok */
\t\tinfo->msg_tree = RB_ROOT;
\t\tinfo->node_cache = NULL;
\t\tmemset(&info->attr, 0, sizeof(info->attr));
\t\tinfo->attr.mq_maxmsg = min(ipc_ns->mq_msg_max,
\t\t\t\t\t ipc_ns->mq_msg_default);
\t\tinfo->attr.mq_msgsize = min(ipc_ns->mq_msgsize_max,
\t\t\t\t\t ipc_ns->mq_msgsize_default);
\t\tif (attr) {
\t\t\tinfo->attr.mq_maxmsg = attr->mq_maxmsg;
\t\t\tinfo->attr.mq_msgsize = attr->mq_msgsize;
\t\t}
\t
\t\tmq_treesize = info->attr.mq_maxmsg * sizeof(struct msg_msg) +
\t\t\tmin_t(unsigned int, info->attr.mq_maxmsg, MQ_PRIO_MAX) *
\t\t\tsizeof(struct posix_msg_tree_node);
\t\tmq_bytes = mq_treesize + (info->attr.mq_maxmsg *
\t\t\t\t\t info->attr.mq_msgsize);
\t\tspin_lock(&mq_lock);
//下面這個坑爹的判斷,同一個用戶創建的所有queue的需要的內存大小如果超過了當前進程的最大queue的大小限制的話,就會失敗。錯誤碼24!!!!!!!!!!!!
\t\tif (u->mq_bytes + mq_bytes < u->mq_bytes ||
\t\t u->mq_bytes + mq_bytes > rlimit(RLIMIT_MSGQUEUE)) {
\t\t\tspin_unlock(&mq_lock);
\t\t\t/* mqueue_evict_inode() releases info->messages */
\t\t\tret = -EMFILE;//錯誤碼24!!!!!!!!!!!!!
\t\t\tgoto out_inode;
\t\t}
\t\tu->mq_bytes += mq_bytes;
\t\tspin_unlock(&mq_lock);
\t\t/* all is ok */

\t\tinfo->user = get_uid(u);
\t} else if (S_ISDIR(mode)) {
\t\tinc_nlink(inode);
\t\t/* Some things misbehave if size == 0 on a directory */
\t\tinode->i_size = 2 * DIRENT_SIZE;
\t\tinode->i_op = &mqueue_dir_inode_operations;
\t\tinode->i_fop = &simple_dir_operations;
\t}
\treturn inode;
out_inode:
\tiput(inode);
err:
\treturn ERR_PTR(ret);
}

到這裡,原因已經找到了。因此,將所有進程的RLIMIT_MSGQUEUE搞大就可以解決問題。這些進程都是由同一個父進程啟動的,因此只要在父進程中修改RLIMIT_MSGQUEUE。

但是這裡還有最後一個問題,為什麼去掉A進程的優先級後,也會影響queue size的限制呢?

查看A進程的代碼返現,它中間調用瞭如下代碼改變了自己創建的queue的所有者,去掉實時調度優先級後,下面的代碼來不及執行,加大了出現問題的概率,(A進程有實時調度也會出現該問題,只是概率很小),坑爹的外包啊。

if (chown(inputChannel.c_str(), 0, userInfo->pw_gid) == -1) {
\t\t\tMM_LOG_ERROR(ATB3_CONTEXT_INBOUND, MM_MSG("Failed to change group id on '" << inputChannel << "' (" << strerror(errno) << ")."));
\t\t\tthrow std::runtime_error("Failed to change group id");
\t\t}
芯片運行在極限的邊緣,怪異問題集(一)創建消息隊列,錯誤碼24


分享到:


相關文章: