节点位置

/sys/module/workqueue/parameters/power_efficient

批判一下

大量第三方”调度模块“和”内核调整软件“都在使用这个节点并打出”工作队列优化“、”对齐工作队列“等名号。
但实际上,在正常情况下,修改这个节点的值并不会产生任何效果。

分析

权限

首先这个节点的默认权限是444(只读不可写)
大量第三方开发者会刻意改变这个节点的权限并认为这是linux的一个bug(( https://github.com/xzr467706992/android_kernel_zuk_msm8996/commit/6e339627c1894fb48b44749531ae56bebc2e8f4a
模块在写入这个节点之前也会先来个chmod

但是,实际上,这个节点的444权限是有意为之而不是一个失误

源码分析

在workqueue.c中有以下定义

/* see the comment above the definition of WQ_POWER_EFFICIENT */
static bool wq_power_efficient = IS_ENABLED(CONFIG_WQ_POWER_EFFICIENT_DEFAULT);
module_param_named(power_efficient, wq_power_efficient, bool, 0444);

这个节点指向内核中的wq_power_efficient变量,而这个变量的默认赋值为CONFIG_WQ_POWER_EFFICIENT_DEFAULT。这也就指明了这项功能的标准启动方式,便是在config中设置CONFIG_WQ_POWER_EFFICIENT_DEFAULT=yhttps://github.com/xzr467706992/android_kernel_oneplus_sm8250/commit/3852568f28cf11b0d0d14c4c41cfc0cab82c8912#diff-09d9716c40d9ac7ae4c14fd4753849b1
那么问题来了,一个是由config向这个变量赋值,另一个是由用户空间向这个变量写入值,都是在控制一个东西,还能有区别?别急我们接着往下看。

这个变量的再次出现,便是在workqueue.c的__alloc_workqueue_key函数中,而这,也是这个变量的唯一一次调用。

    /* see the comment above the definition of WQ_POWER_EFFICIENT */
    if ((flags & WQ_POWER_EFFICIENT) && wq_power_efficient)
        flags |= WQ_UNBOUND;

从这里我们可以看到这个变量的作用:如果一个工作队列定义了WQ_POWER_EFFICIENT(一个工作队列的可选特性),并且wq_power_efficient处于打开状态,那么就为这个工作队列增加特性WQ_UNBOUND,至于这东西的作用,我们下面再说。
从这里来看,似乎并没有问题,这个变量也被调用到了,并且它也不是hardcode,那我为什么会说从用户空间写入它是无效的呢?
我们接着往下看
在workqueue.h中,__alloc_workqueue_key函数被使用了

#ifdef CONFIG_LOCKDEP
#define alloc_workqueue(fmt, flags, max_active, args...)        \
({                                    \
    static struct lock_class_key __key;                \
    const char *__lock_name;                    \
                                    \
    __lock_name = "(wq_completion)"#fmt#args;            \
                                    \
    __alloc_workqueue_key((fmt), (flags), (max_active),        \
                  &__key, __lock_name, ##args);        \
})
#else
#define alloc_workqueue(fmt, flags, max_active, args...)        \
    __alloc_workqueue_key((fmt), (flags), (max_active),        \
                  NULL, NULL, ##args)
#endif

这里的ifdef预处理就不用看了,反正只需要知道,调用alloc_workqueue必将调用__alloc_workqueue_key
那么我们继续找alloc_workqueue是在何处调用的
就在上面那个定义的下面几行的地方,出现了alloc_workqueue的使用

#define alloc_ordered_workqueue(fmt, flags, args...)            \
    alloc_workqueue(fmt, WQ_UNBOUND | __WQ_ORDERED |        \
            __WQ_ORDERED_EXPLICIT | (flags), 1, ##args)

#define create_workqueue(name)                        \
    alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
#define create_freezable_workqueue(name)                \
    alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND |    \
            WQ_MEM_RECLAIM, 1, (name))
#define create_singlethread_workqueue(name)                \
    alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

可以看到,函数
alloc_ordered_workqueue
create_workqueue
create_freezable_workqueue
create_singlethread_workqueue
实际上在预处理之后调用的都是alloc_workqueue
然后呢?这似乎没毛病啊?为什么写入变量wq_power_efficient不能改变工作队列的状态呢?

梳理一下现在的调用情况:

alloc_ordered_workqueue   
create_workqueue                 -------------------》alloc_workqueue---》__alloc_workqueue_key--》wq_power_efficient
create_freezable_workqueue -------------------》
create_singlethread_workqueue

嗯?wq_power_efficient完美被调用?
没这么简单

我们再来找一找上面说的这些函数在内核中的调用情况

匹配到二进制文件 out/net/core/sock_diag.o
匹配到二进制文件 out/net/core/net_namespace.o
匹配到二进制文件 out/net/l2tp/l2tp_core.o
匹配到二进制文件 out/net/ipv6/addrconf.o
匹配到二进制文件 out/net/bluetooth/hci_core.o
匹配到二进制文件 out/net/wireless/core.o
匹配到二进制文件 out/net/sched/cls_api.o
匹配到二进制文件 out/net/wireguard/device.o
匹配到二进制文件 out/kernel/cgroup/cgroup-v1.o
匹配到二进制文件 out/kernel/cgroup/cgroup.o
匹配到二进制文件 out/kernel/cgroup/cpuset.o
匹配到二进制文件 out/kernel/rcu/tree.o
匹配到二进制文件 out/kernel/workqueue.o
匹配到二进制文件 out/kernel/power/main.o
匹配到二进制文件 out/fs/fs-writeback.o
匹配到二进制文件 out/fs/direct-io.o
匹配到二进制文件 out/fs/ext4/super.o
匹配到二进制文件 out/fs/crypto/crypto.o
匹配到二进制文件 out/fs/cifs/cifsfs.o
匹配到二进制文件 out/crypto/crypto_wq.o
匹配到二进制文件 out/drivers/char/diag/diagfwd_cntl.o
匹配到二进制文件 out/drivers/char/diag/diagfwd_bridge.o
匹配到二进制文件 out/drivers/char/diag/diag_dci.o
匹配到二进制文件 out/drivers/char/diag/diagfwd.o
匹配到二进制文件 out/drivers/char/diag/diagfwd_mhi.o
匹配到二进制文件 out/drivers/char/diag/diagfwd_socket.o
匹配到二进制文件 out/drivers/char/diag/diagchar_core.o
匹配到二进制文件 out/drivers/char/diag/diagfwd_rpmsg.o
匹配到二进制文件 out/drivers/char/diag/diag_usb.o
匹配到二进制文件 out/drivers/media/platform/msm/synx/synx.o
匹配到二进制文件 out/drivers/media/platform/msm/npu/npu_mgr.o
匹配到二进制文件 out/drivers/media/platform/msm/cvp/cvp_hfi.o
匹配到二进制文件 out/drivers/soc/qcom/rq_stats.o
匹配到二进制文件 out/drivers/soc/qcom/qmi_rmnet.o
匹配到二进制文件 out/drivers/soc/qcom/wda_qmi.o
匹配到二进制文件 out/drivers/soc/qcom/subsystem_restart.o
匹配到二进制文件 out/drivers/soc/qcom/peripheral-loader.o
匹配到二进制文件 out/drivers/soc/qcom/service-notifier.o
匹配到二进制文件 out/drivers/soc/qcom/dfc_qmi.o
匹配到二进制文件 out/drivers/soc/qcom/qdss_bridge.o
匹配到二进制文件 out/drivers/soc/qcom/qmi_interface.o
匹配到二进制文件 out/drivers/soc/qcom/cdsprm.o
匹配到二进制文件 out/drivers/hwtracing/coresight/coresight-byte-cntr.o
匹配到二进制文件 out/drivers/input/touchscreen/synaptics/syna_tcm_oncell/synaptics_tcm_oncell.o
匹配到二进制文件 out/drivers/input/touchscreen/touchpanel_common_driver.o
匹配到二进制文件 out/drivers/md/dm-kcopyd.o
匹配到二进制文件 out/drivers/md/dm.o
匹配到二进制文件 out/drivers/md/dm-verity-target.o
匹配到二进制文件 out/drivers/md/dm-crypt.o
匹配到二进制文件 out/drivers/md/dm-bufio.o
匹配到二进制文件 out/drivers/edac/wq.o
匹配到二进制文件 out/drivers/tty/okl4_vtty.o
匹配到二进制文件 out/drivers/data-kernel/rmnet/shs/rmnet_shs_wq.o
匹配到二进制文件 out/drivers/data-kernel/rmnet/shs/rmnet_shs_freq.o
匹配到二进制文件 out/drivers/thermal/qcom/adc-tm.o
匹配到二进制文件 out/drivers/thermal/thermal_core.o
匹配到二进制文件 out/drivers/thermal/msm-tsens.o
匹配到二进制文件 out/drivers/misc/oneplus/op_rf_cable_monitor.o
匹配到二进制文件 out/drivers/i3c/master.o
匹配到二进制文件 out/drivers/gpu/msm/kgsl_pwrscale.o
匹配到二进制文件 out/drivers/gpu/msm/kgsl.o
匹配到二进制文件 out/drivers/usb/core/hub.o
匹配到二进制文件 out/drivers/usb/dwc3/dwc3-msm.o
匹配到二进制文件 out/drivers/usb/dwc3/core.o
匹配到二进制文件 out/drivers/usb/gadget/function/f_fs.o
匹配到二进制文件 out/drivers/usb/gadget/function/f_qdss.o
匹配到二进制文件 out/drivers/usb/gadget/function/f_gsi.o
匹配到二进制文件 out/drivers/usb/gadget/function/f_cdev.o
匹配到二进制文件 out/drivers/usb/gadget/function/f_mtp.o
匹配到二进制文件 out/drivers/usb/misc/ssusb-redriver-nb7vpq904m.o
匹配到二进制文件 out/drivers/usb/pd/policy_engine.o
匹配到二进制文件 out/drivers/mmc/core/block.o
匹配到二进制文件 out/drivers/mmc/host/sdhci-msm.o
匹配到二进制文件 out/drivers/bus/mhi/core/mhi_init.o
匹配到二进制文件 out/drivers/bus/mhi/controllers/mhi_qcom.o
匹配到二进制文件 out/drivers/staging/qcacld-3.0/core/cds/src/cds_api.o
匹配到二进制文件 out/drivers/staging/qca-wifi-host-cmn/wmi/src/wmi_unified.o
匹配到二进制文件 out/drivers/nfc/pn553.o
匹配到二进制文件 out/drivers/platform/msm/usb_bam.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_clients/ipa_usb.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_clients/ipa_mhi_client.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_rm.o
匹配到二进制文件 out/drivers/platform/msm/ipa/test/ipa_ut_framework.o
匹配到二进制文件 out/drivers/platform/msm/ipa/test/ipa_test_dma.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_v3/ipa.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_v3/ipa_dp.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_v3/ipa_pm.o
匹配到二进制文件 out/drivers/platform/msm/ipa/ipa_v3/ipa_interrupts.o
匹配到二进制文件 out/drivers/platform/msm/gsi/gsi_dbg.o
匹配到二进制文件 out/drivers/net/wireless/ath/wil6210/main.o
匹配到二进制文件 out/drivers/net/wireless/cnss2/main.o
匹配到二进制文件 out/drivers/net/bonding/bond_main.o
匹配到二进制文件 out/drivers/crypto/msm/qcrypto.o
匹配到二进制文件 out/drivers/scsi/ufs/ufs-qcom.o
匹配到二进制文件 out/drivers/scsi/ufs/ufshcd.o
匹配到二进制文件 out/drivers/scsi/ufs/ufs-qcom-ice.o
匹配到二进制文件 out/drivers/scsi/hosts.o
匹配到二进制文件 out/drivers/power/supply/qcom/bq27541_fuelgauger.o
匹配到二进制文件 out/drivers/vservices/session.o
匹配到二进制文件 out/drivers/vservices/transport/axon.o
匹配到二进制文件 out/drivers/esoc/esoc-mdm-4x.o
匹配到二进制文件 out/drivers/esoc/esoc-mdm-drv.o
匹配到二进制文件 out/drivers/devfreq/governor_msm_adreno_tz.o
匹配到二进制文件 out/drivers/devfreq/devfreq.o
匹配到二进制文件 out/drivers/devfreq/arm-memlat-mon.o
匹配到二进制文件 out/drivers/slimbus/slimbus.o
匹配到二进制文件 out/mm/vmstat.o
匹配到二进制文件 out/mm/memcontrol.o
匹配到二进制文件 out/mm/backing-dev.o
匹配到二进制文件 out/sound/usb/usb_audio_qmi_svc.o
..............................................

芜湖,好多

我们再随意打开一个文件看看
devfreq.c

static int __init devfreq_init(void)
{
    devfreq_class = class_create(THIS_MODULE, "devfreq");
    if (IS_ERR(devfreq_class)) {
        pr_err("%s: couldn't create class\n", __FILE__);
        return PTR_ERR(devfreq_class);
    }

    devfreq_wq = create_freezable_workqueue("devfreq_wq");
    if (!devfreq_wq) {
        class_destroy(devfreq_class);
        pr_err("%s: couldn't create workqueue\n", __FILE__);
        return -ENOMEM;
    }
    devfreq_class->dev_groups = devfreq_groups;

    return 0;
}
subsys_initcall(devfreq_init);

再来一个
touchpanel_common_driver.c

int register_common_touch_device(struct touchpanel_data *pdata)
{
.................
ts->async_workqueue = create_singlethread_workqueue("tp_async");
    if (!ts->async_workqueue) {
        ret = -ENOMEM;
        return -1;
    }
................
}

嗯?有没有找到什么共同点?
没错,工作队列的注册基本都发生在内核启动的早期阶段。
然后这意味着什么?
这意味着当你在用户空间改变/sys/module/workqueue/parameters/power_efficient的节点值时,驱动中的工作队列早就注册完毕了,你压根就无法改变它们的初始化过程。。。
你以为只要值改变的足够早,早到magisk的post-fs就能改变这个过程了?
想peech呢,内核中最晚的late_init也要比post-fs早多了。。安卓系统想要启动先得等内核启动完毕

所以,不是这个节点的值无法改变什么东西,而是在你改变了节点的值之后,没什么东西能被你改变了。
( 当然,某些很晚/动态创建工作队列的变态驱动除外(不过我还没看到过这样的驱动) )

WQ_UNBOUND

从这里我们可以看到这个变量的作用:如果一个工作队列定义了WQ_POWER_EFFICIENT(一个工作队列的可选特性),并且wq_power_efficient处于打开状态,那么就为这个工作队列增加特性WQ_UNBOUND,至于这东西的作用,我们下面再说。

接一下这个东西
就不源码分析了
直接说
标记了WQ_UNBOUND的工作队列可以被调度器调度到不同的CPU上
调度器会尽量避免唤醒已经进入C-state的CPU,从而节省电量
没有标记的WQ_UNBOUND的工作队列只会运行在单个CPU上,这可以使工作队列受益于CPU缓存中的适宜数据,从而提升任务处理效率
说白了,这是个省电与性能之间的平衡问题
wq_power_efficient正是这个平衡中的可调参数
在没有开启wq_power_efficient的情况下,倾向性能,被标注为WQ_POWER_EFFICIENT的任务不可被调度
在开启wq_power_efficient的情况下,WQ_POWER_EFFICIENT=WQ_UNBOUND,任务可被调度,追求省电

总结

内核启动完成后调整/sys/module/workqueue/parameters/power_efficient不会对现有工作队列产生任何影响,这正是这个节点权限444的根本原因
如果想要开启wq_power_efficient,请在config中开启CONFIG_WQ_POWER_EFFICIENT_DEFAULT