【无标题】ethtool程序,从用户态调用内核态调用分析

2023-10-13 13:01

本文主要是介绍【无标题】ethtool程序,从用户态调用内核态调用分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 说明

  • 用户层ethtool使用的是: ethtool-6.5 为例,下载来自:
    https://mirrors.edge.kernel.org/pub/software/network/ethtool/

  • 内核层:在内核源码中,VSCode -->【ctrl+p】搜索文件:linux/ethtool.h及 core/ethtool.c,相关实现在这两个文件中。

2. 应用层 ethtool-6.5

  • ethtool.c
    下面的 args 结构体展示了成员的选项(.opts)、执行函数(.func)、netlink执行函数(.nlfunc,暂时不必关心)、帮助(.xhelp)。
    通过他们,能看出ethtool命令行后面跟什么选项时,调用了什么函数!
// 
static const struct option args[] = {{/* "default" entry when no switch is used */.opts	= "",.func	= do_gset,.nlfunc	= nl_gset,.help	= "Display standard information about device",},{.opts	= "-s|--change",.func	= do_sset,.nlfunc	= nl_sset,.help	= "Change generic options",.xhelp	= "		[ speed %d ]\n""		[ lanes %d ]\n""		[ duplex half|full ]\n""		[ port tp|aui|bnc|mii|fibre|da ]\n""		[ mdix auto|on|off ]\n""		[ autoneg on|off ]\n""		[ advertise %x[/%x] | mode on|off ... [--] ]\n""		[ phyad %d ]\n""		[ xcvr internal|external ]\n""		[ wol %d[/%d] | p|u|m|b|a|g|s|f|d... ]\n""		[ sopass %x:%x:%x:%x:%x:%x ]\n""		[ msglvl %d[/%d] | type on|off ... [--] ]\n""		[ master-slave preferred-master|preferred-slave|forced-master|forced-slave ]\n"},{.opts	= "-a|--show-pause",.json	= true,.func	= do_gpause,.nlfunc	= nl_gpause,.help	= "Show pause options",.xhelp	= "		[ --src aggregate | emac | pmac ]\n"},{.opts	= "-A|--pause",.func	= do_spause,.nlfunc	= nl_spause,.help	= "Set pause options",.xhelp	= "		[ autoneg on|off ]\n""		[ rx on|off ]\n""		[ tx on|off ]\n"},{.opts	= "-c|--show-coalesce",.json	= true,.func	= do_gcoalesce,.nlfunc	= nl_gcoalesce,.help	= "Show coalesce options"},{.opts	= "-C|--coalesce",.func	= do_scoalesce,.nlfunc	= nl_scoalesce,.help	= "Set coalesce options",.xhelp	= "		[adaptive-rx on|off]\n""		[adaptive-tx on|off]\n""		[rx-usecs N]\n""		[rx-frames N]\n""		[rx-usecs-irq N]\n""		[rx-frames-irq N]\n""		[tx-usecs N]\n""		[tx-frames N]\n""		[tx-usecs-irq N]\n""		[tx-frames-irq N]\n""		[stats-block-usecs N]\n""		[pkt-rate-low N]\n""		[rx-usecs-low N]\n""		[rx-frames-low N]\n""		[tx-usecs-low N]\n""		[tx-frames-low N]\n""		[pkt-rate-high N]\n""		[rx-usecs-high N]\n""		[rx-frames-high N]\n""		[tx-usecs-high N]\n""		[tx-frames-high N]\n""		[sample-interval N]\n""		[cqe-mode-rx on|off]\n""		[cqe-mode-tx on|off]\n""		[tx-aggr-max-bytes N]\n""		[tx-aggr-max-frames N]\n""		[tx-aggr-time-usecs N]\n"},{.opts	= "-g|--show-ring",.json	= true,.func	= do_gring,.nlfunc	= nl_gring,.help	= "Query RX/TX ring parameters"},{.opts	= "-G|--set-ring",.func	= do_sring,.nlfunc	= nl_sring,.help	= "Set RX/TX ring parameters",.xhelp	= "		[ rx N ]\n""		[ rx-mini N ]\n""		[ rx-jumbo N ]\n""		[ tx N ]\n""		[ rx-buf-len N ]\n""		[ cqe-size N ]\n""		[ tx-push on|off ]\n""		[ rx-push on|off ]\n""		[ tx-push-buf-len N]\n"},{.opts	= "-k|--show-features|--show-offload",.json	= true,.func	= do_gfeatures,.nlfunc	= nl_gfeatures,.help	= "Get state of protocol offload and other features"},{.opts	= "-K|--features|--offload",.func	= do_sfeatures,.nlfunc	= nl_sfeatures,.help	= "Set protocol offload and other features",.xhelp	= "		FEATURE on|off ...\n"},{.opts	= "-i|--driver",.func	= do_gdrv,.help	= "Show driver information"},{.opts	= "-d|--register-dump",.func	= do_gregs,.help	= "Do a register dump",.xhelp	= "		[ raw on|off ]\n""		[ file FILENAME ]\n"},{.opts	= "-e|--eeprom-dump",.func	= do_geeprom,.help	= "Do a EEPROM dump",.xhelp	= "		[ raw on|off ]\n""		[ offset N ]\n""		[ length N ]\n"},{.opts	= "-E|--change-eeprom",.func	= do_seeprom,.help	= "Change bytes in device EEPROM",.xhelp	= "		[ magic N ]\n""		[ offset N ]\n""		[ length N ]\n""		[ value N ]\n"},{.opts	= "-r|--negotiate",.func	= do_nway_rst,.help	= "Restart N-WAY negotiation"},{.opts	= "-p|--identify",.func	= do_phys_id,.help	= "Show visible port identification (e.g. blinking)",.xhelp	= "		[ TIME-IN-SECONDS ]\n"},{.opts	= "-t|--test",.func	= do_test,.help	= "Execute adapter self test",.xhelp	= "		[ online | offline | external_lb ]\n"},{.opts	= "-S|--statistics",.json	= true,.func	= do_gnicstats,.nlchk	= nl_gstats_chk,.nlfunc	= nl_gstats,.help	= "Show adapter statistics",.xhelp	= "		[ --all-groups | --groups [eth-phy] [eth-mac] [eth-ctrl] [rmon] ]\n""		[ --src aggregate | emac | pmac ]\n"},{.opts	= "--phy-statistics",.func	= do_gphystats,.help	= "Show phy statistics"},{.opts	= "-n|-u|--show-nfc|--show-ntuple",.func	= do_grxclass,.help	= "Show Rx network flow classification options or rules",.xhelp	= "		[ rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|""tcp6|udp6|ah6|esp6|sctp6 [context %d] |\n""		  rule %d ]\n"},{.opts	= "-N|-U|--config-nfc|--config-ntuple",.func	= do_srxclass,.help	= "Configure Rx network flow classification options or rules",.xhelp	= "		rx-flow-hash tcp4|udp4|ah4|esp4|sctp4|""tcp6|udp6|ah6|esp6|sctp6 m|v|t|s|d|f|n|r... [context %d] |\n""		flow-type ether|ip4|tcp4|udp4|sctp4|ah4|esp4|""ip6|tcp6|udp6|ah6|esp6|sctp6\n""			[ src %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n""			[ dst %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n""			[ proto %d [m %x] ]\n""			[ src-ip IP-ADDRESS [m IP-ADDRESS] ]\n""			[ dst-ip IP-ADDRESS [m IP-ADDRESS] ]\n""			[ tos %d [m %x] ]\n""			[ tclass %d [m %x] ]\n""			[ l4proto %d [m %x] ]\n""			[ src-port %d [m %x] ]\n""			[ dst-port %d [m %x] ]\n""			[ spi %d [m %x] ]\n""			[ vlan-etype %x [m %x] ]\n""			[ vlan %x [m %x] ]\n""			[ user-def %x [m %x] ]\n""			[ dst-mac %x:%x:%x:%x:%x:%x [m %x:%x:%x:%x:%x:%x] ]\n""			[ action %d ] | [ vf %d queue %d ]\n""			[ context %d ]\n""			[ loc %d ] |\n""		delete %d\n"},{.opts	= "-T|--show-time-stamping",.func	= do_tsinfo,.nlfunc	= nl_tsinfo,.help	= "Show time stamping capabilities"},{.opts	= "-x|--show-rxfh-indir|--show-rxfh",.json	= true,.func	= do_grxfh,.nlfunc	= nl_grss,.help	= "Show Rx flow hash indirection table and/or RSS hash key",.xhelp	= "		[ context %d ]\n"},{.opts	= "-X|--set-rxfh-indir|--rxfh",.func	= do_srxfh,.help	= "Set Rx flow hash indirection table and/or RSS hash key",.xhelp	= "		[ context %d|new ]\n""		[ equal N | weight W0 W1 ... | default ]\n""		[ hkey %x:%x:%x:%x:%x:.... ]\n""		[ hfunc FUNC ]\n""		[ delete ]\n"},{.opts	= "-f|--flash",.func	= do_flash,.help	= "Flash firmware image from the specified file to a region on the device",.xhelp	= "		FILENAME [ REGION-NUMBER-TO-FLASH ]\n"},{.opts	= "-P|--show-permaddr",.func	= do_permaddr,.nlfunc	= nl_permaddr,.help	= "Show permanent hardware address"},{.opts	= "-w|--get-dump",.func	= do_getfwdump,.help	= "Get dump flag, data",.xhelp	= "		[ data FILENAME ]\n"},{.opts	= "-W|--set-dump",.func	= do_setfwdump,.help	= "Set dump flag of the device",.xhelp	= "		N\n"},{.opts	= "-l|--show-channels",.func	= do_gchannels,.nlfunc	= nl_gchannels,.help	= "Query Channels"},{.opts	= "-L|--set-channels",.func	= do_schannels,.nlfunc	= nl_schannels,.help	= "Set Channels",.xhelp	= "		[ rx N ]\n""		[ tx N ]\n""		[ other N ]\n""		[ combined N ]\n"},{.opts	= "--show-priv-flags",.func	= do_gprivflags,.nlfunc	= nl_gprivflags,.help	= "Query private flags"},{.opts	= "--set-priv-flags",.func	= do_sprivflags,.nlfunc	= nl_sprivflags,.help	= "Set private flags",.xhelp	= "		FLAG on|off ...\n"},{.opts	= "-m|--dump-module-eeprom|--module-info",.func	= do_getmodule,.nlfunc = nl_getmodule,.help	= "Query/Decode Module EEPROM information and optical diagnostics if available",.xhelp	= "		[ raw on|off ]\n""		[ hex on|off ]\n""		[ offset N ]\n""		[ length N ]\n""		[ page N ]\n""		[ bank N ]\n""		[ i2c N ]\n"},{.opts	= "--show-eee",.func	= do_geee,.nlfunc	= nl_geee,.help	= "Show EEE settings",},{.opts	= "--set-eee",.func	= do_seee,.nlfunc	= nl_seee,.help	= "Set EEE settings",.xhelp	= "		[ eee on|off ]\n""		[ advertise %x ]\n""		[ tx-lpi on|off ]\n""		[ tx-timer %d ]\n"},{.opts	= "--set-phy-tunable",.func	= do_set_phy_tunable,.help	= "Set PHY tunable",.xhelp	= "		[ downshift on|off [count N] ]\n""		[ fast-link-down on|off [msecs N] ]\n""		[ energy-detect-power-down on|off [msecs N] ]\n"},{.opts	= "--get-phy-tunable",.func	= do_get_phy_tunable,.help	= "Get PHY tunable",.xhelp	= "		[ downshift ]\n""		[ fast-link-down ]\n""		[ energy-detect-power-down ]\n"},{.opts	= "--get-tunable",.func	= do_gtunable,.help	= "Get tunable",.xhelp	= "		[ rx-copybreak ]\n""		[ tx-copybreak ]\n""		[ tx-buf-size ]\n""		[ pfc-prevention-tout ]\n"},{.opts	= "--set-tunable",.func	= do_stunable,.help	= "Set tunable",.xhelp	= "		[ rx-copybreak N ]\n""		[ tx-copybreak N ]\n""		[ tx-buf-size N ]\n""		[ pfc-prevention-tout N ]\n"},{.opts	= "--reset",.func	= do_reset,.help	= "Reset components",.xhelp	= "		[ flags %x ]\n""		[ mgmt ]\n""		[ mgmt-shared ]\n""		[ irq ]\n""		[ irq-shared ]\n""		[ dma ]\n""		[ dma-shared ]\n""		[ filter ]\n""		[ filter-shared ]\n""		[ offload ]\n""		[ offload-shared ]\n""		[ mac ]\n""		[ mac-shared ]\n""		[ phy ]\n""		[ phy-shared ]\n""		[ ram ]\n""		[ ram-shared ]\n""		[ ap ]\n""		[ ap-shared ]\n""		[ dedicated ]\n""		[ all ]\n"},{.opts	= "--show-fec",.json	= true,.func	= do_gfec,.nlfunc	= nl_gfec,.help	= "Show FEC settings",},{.opts	= "--set-fec",.func	= do_sfec,.nlfunc	= nl_sfec,.help	= "Set FEC settings",.xhelp	= "		[ encoding auto|off|rs|baser|llrs [...] ]\n"},{.opts	= "-Q|--per-queue",.func	= do_perqueue,.help	= "Apply per-queue command. ",.xhelp	= "The supported sub commands include --show-coalesce, --coalesce""		[queue_mask %x] SUB_COMMAND\n",},{.opts	= "--cable-test",.json	= true,.nlfunc	= nl_cable_test,.help	= "Perform a cable test",},{.opts	= "--cable-test-tdr",.json	= true,.nlfunc	= nl_cable_test_tdr,.help	= "Print cable test time domain reflectrometery data",.xhelp	= "		[ first N ]\n""		[ last N ]\n""		[ step N ]\n""		[ pair N ]\n"},{.opts	= "--show-tunnels",.nlfunc	= nl_gtunnels,.help	= "Show NIC tunnel offload information",},{.opts	= "--show-module",.json	= true,.nlfunc	= nl_gmodule,.help	= "Show transceiver module settings",},{.opts	= "--set-module",.nlfunc	= nl_smodule,.help	= "Set transceiver module settings",.xhelp	= "		[ power-mode-policy high|auto ]\n"},{.opts	= "--get-plca-cfg",.nlfunc	= nl_plca_get_cfg,.help	= "Get PLCA configuration",},{.opts	= "--set-plca-cfg",.nlfunc	= nl_plca_set_cfg,.help	= "Set PLCA configuration",.xhelp  = "		[ enable on|off ]\n""		[ node-id N ]\n""		[ node-cnt N ]\n""		[ to-tmr N ]\n""		[ burst-cnt N ]\n""		[ burst-tmr N ]\n"},{.opts	= "--get-plca-status",.nlfunc	= nl_plca_get_status,.help	= "Get PLCA status information",},{.opts	= "--show-mm",.json	= true,.nlfunc	= nl_get_mm,.help	= "Show MAC merge layer state",},{.opts	= "--set-mm",.nlfunc	= nl_set_mm,.help	= "Set MAC merge layer parameters","		[ verify-enabled on|off ]\n""		[ verify-time N ]\n""		[ tx-enabled on|off ]\n""		[ pmac-enabled on|off ]\n""		[ tx-min-frag-size 60-252 ]\n"},{.opts	= "--show-pse",.json	= true,.nlfunc	= nl_gpse,.help	= "Show settings for Power Sourcing Equipment",},{.opts	= "--set-pse",.nlfunc	= nl_spse,.help	= "Set Power Sourcing Equipment settings",.xhelp	= "		[ podl-pse-admin-control enable|disable ]\n"},{.opts	= "-h|--help",.no_dev	= true,.func	= show_usage,.help	= "Show this help"},{.opts	= "--version",.no_dev	= true,.func	= do_version,.help	= "Show version number"},{}
};

读操作

这里以 .opts = “-i|–driver” 为例:读取网卡信息

#使用:ethtool -i ethX
do_gdrv--> send_ioctl(ETHTOOL_GDRVINFO)--> ioctl(SIOCETHTOOL);

写操作

这里以 .opts = "-p|–identify"为例,设置网卡点灯时间

#使用:ethtool -p ethX 10
do_phys_id--> send_ioctl(ETHTOOL_PHYS_ID);--> ioctl(SIOCETHTOOL);

应用层调用到 ioctl ,到此为止!

3. 内核层

继续上面示例,应用层 ioctl 继续调用内核层 dev_ioctl:

读操作:读取网卡信息

dev_ioctl case SIOCETHTOOL:-->--> dev_ethtool--> ethtool_get_drvinfo--> ops->get_drvinfo  #这个函数是 struct ethtool_ops 结构体中,需要自己实现

写操作:设置网卡点灯时间

dev_ioctl case SIOCETHTOOL:-->--> dev_ethtool--> ethtool_phys_id--> ops->set_phys_id  #这个函数是 struct ethtool_ops 结构体中,需要自己实现

struct ethtool_ops 内容(linux/ethtool.h)

struct ethtool_ops {{int	(*get_settings)(struct net_device *, struct ethtool_cmd *);int	(*set_settings)(struct net_device *, struct ethtool_cmd *);void	(*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *);int	(*get_regs_len)(struct net_device *);void	(*get_regs)(struct net_device *, struct ethtool_regs *, void *);void	(*get_wol)(struct net_device *, struct ethtool_wolinfo *);int	(*set_wol)(struct net_device *, struct ethtool_wolinfo *);u32	(*get_msglevel)(struct net_device *);void	(*set_msglevel)(struct net_device *, u32);int	(*nway_reset)(struct net_device *);u32	(*get_link)(struct net_device *);int	(*get_eeprom_len)(struct net_device *);int	(*get_eeprom)(struct net_device *,struct ethtool_eeprom *, u8 *);int	(*set_eeprom)(struct net_device *,struct ethtool_eeprom *, u8 *);int	(*get_coalesce)(struct net_device *, struct ethtool_coalesce *);int	(*set_coalesce)(struct net_device *, struct ethtool_coalesce *);void	(*get_ringparam)(struct net_device *,struct ethtool_ringparam *);int	(*set_ringparam)(struct net_device *,struct ethtool_ringparam *);void	(*get_pauseparam)(struct net_device *,struct ethtool_pauseparam*);int	(*set_pauseparam)(struct net_device *,struct ethtool_pauseparam*);void	(*self_test)(struct net_device *, struct ethtool_test *, u64 *);void	(*get_strings)(struct net_device *, u32 stringset, u8 *);int	(*set_phys_id)(struct net_device *, enum ethtool_phys_id_state);void	(*get_ethtool_stats)(struct net_device *,struct ethtool_stats *, u64 *);int	(*begin)(struct net_device *);void	(*complete)(struct net_device *);u32	(*get_priv_flags)(struct net_device *);int	(*set_priv_flags)(struct net_device *, u32);int	(*get_sset_count)(struct net_device *, int);int	(*get_rxnfc)(struct net_device *,struct ethtool_rxnfc *, u32 *rule_locs);int	(*set_rxnfc)(struct net_device *, struct ethtool_rxnfc *);int	(*flash_device)(struct net_device *, struct ethtool_flash *);int	(*reset)(struct net_device *, u32 *);u32	(*get_rxfh_key_size)(struct net_device *);u32	(*get_rxfh_indir_size)(struct net_device *);int	(*get_rxfh)(struct net_device *, u32 *indir, u8 *key,u8 *hfunc);int	(*set_rxfh)(struct net_device *, const u32 *indir,const u8 *key, const u8 hfunc);void	(*get_channels)(struct net_device *, struct ethtool_channels *);int	(*set_channels)(struct net_device *, struct ethtool_channels *);int	(*get_dump_flag)(struct net_device *, struct ethtool_dump *);int	(*get_dump_data)(struct net_device *,struct ethtool_dump *, void *);int	(*set_dump)(struct net_device *, struct ethtool_dump *);int	(*get_ts_info)(struct net_device *, struct ethtool_ts_info *);int     (*get_module_info)(struct net_device *,struct ethtool_modinfo *);int     (*get_module_eeprom)(struct net_device *,struct ethtool_eeprom *, u8 *);int	(*get_eee)(struct net_device *, struct ethtool_eee *);int	(*set_eee)(struct net_device *, struct ethtool_eee *);int	(*get_tunable)(struct net_device *,const struct ethtool_tunable *, void *);int	(*set_tunable)(struct net_device *,const struct ethtool_tunable *, const void *);};

ethtool_ops使用的典型参考:dm9000.c

在内核源码中,VScode–【ctrl+p】,输入 dm9000.c

static const struct ethtool_ops dm9000_ethtool_ops = {.get_drvinfo		= dm9000_get_drvinfo,.get_settings		= dm9000_get_settings,.set_settings		= dm9000_set_settings,.get_msglevel		= dm9000_get_msglevel,.set_msglevel		= dm9000_set_msglevel,.nway_reset		= dm9000_nway_reset,.get_link		= dm9000_get_link,.get_wol		= dm9000_get_wol,.set_wol		= dm9000_set_wol,.get_eeprom_len		= dm9000_get_eeprom_len,.get_eeprom		= dm9000_get_eeprom,.set_eeprom		= dm9000_set_eeprom,
};

这篇关于【无标题】ethtool程序,从用户态调用内核态调用分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/203411

相关文章

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

Java面试八股之怎么通过Java程序判断JVM是32位还是64位

怎么通过Java程序判断JVM是32位还是64位 可以通过Java程序内部检查系统属性来判断当前运行的JVM是32位还是64位。以下是一个简单的方法: public class JvmBitCheck {public static void main(String[] args) {String arch = System.getProperty("os.arch");String dataM

[职场] 公务员的利弊分析 #知识分享#经验分享#其他

公务员的利弊分析     公务员作为一种稳定的职业选择,一直备受人们的关注。然而,就像任何其他职业一样,公务员职位也有其利与弊。本文将对公务员的利弊进行分析,帮助读者更好地了解这一职业的特点。 利: 1. 稳定的职业:公务员职位通常具有较高的稳定性,一旦进入公务员队伍,往往可以享受到稳定的工作环境和薪资待遇。这对于那些追求稳定的人来说,是一个很大的优势。 2. 薪资福利优厚:公务员的薪资和

一道经典Python程序样例带你飞速掌握Python的字典和列表

Python中的列表(list)和字典(dict)是两种常用的数据结构,它们在数据组织和存储方面有很大的不同。 列表(List) 列表是Python中的一种有序集合,可以随时添加和删除其中的元素。列表中的元素可以是任何数据类型,包括数字、字符串、其他列表等。列表使用方括号[]表示,元素之间用逗号,分隔。 定义和使用 # 定义一个列表 fruits = ['apple', 'banana

高度内卷下,企业如何通过VOC(客户之声)做好竞争分析?

VOC,即客户之声,是一种通过收集和分析客户反馈、需求和期望,来洞察市场趋势和竞争对手动态的方法。在高度内卷的市场环境下,VOC不仅能够帮助企业了解客户的真实需求,还能为企业提供宝贵的竞争情报,助力企业在竞争中占据有利地位。 那么,企业该如何通过VOC(客户之声)做好竞争分析呢?深圳天行健企业管理咨询公司解析如下: 首先,要建立完善的VOC收集机制。这包括通过线上渠道(如社交媒体、官网留言

美容美发店营销版微信小程序源码

打造线上生意新篇章 一、引言:微信小程序,开启美容美发行业新纪元 在数字化时代,微信小程序以其便捷、高效的特点,成为了美容美发行业营销的新宠。本文将带您深入了解美容美发营销微信小程序,探讨其独特优势及如何助力商家实现业务增长。 二、微信小程序:美容美发行业的得力助手 拓宽客源渠道:微信小程序基于微信社交平台,轻松实现线上线下融合,帮助商家快速吸引潜在客户,拓宽客源渠道。 提升用户体验:

程序人生--拔丝地瓜

一个会享受生活的人,难免会执迷于探索“三餐茶饭,四季衣裳”的朴素涵义。如今在这繁杂喧闹、竞争激烈的社会环境里,如何才能从周而复始的生活中挖掘出一点儿期待!这是一个仁者见仁智者见智的开放性话题。对于大部分的人来说,看电影、运动、旅游、美食、加班....是假日的备选安排。 春节临走之前,再次尝试“拔丝地瓜”,为何要强调“再次”二字?因为这道甜菜我已经尝试过很多次,失败与成功都经历过。十几年的烧饭经历

ScrollView 非手动调用的方法

1. /**  *  非人为的时候调用这个方法  *  *  @param scrollView <#scrollView description#>  */ - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {           } 2.判断控制器的view是否加载过 [willShowVC

打包体积分析和优化

webpack分析工具:webpack-bundle-analyzer 1. 通过<script src="./vue.js"></script>方式引入vue、vuex、vue-router等包(CDN) // webpack.config.jsif(process.env.NODE_ENV==='production') {module.exports = {devtool: 'none