cfg80211是怎么配置无线设备的AP的?

news2024/10/1 3:23:37

cfg80211

概念

        cfg80211 是 Linux 内核中一个用于无线网络的配置和管理的子系统,它为多个无线网络驱动提供了一个统一的接口,以便于无线设备的配置和操作。cfg80211 的设计目标是提供一个简化的管理和配置无线设备的框架,同时支持多种无线设备的标准和功能,其在内核中的/net/wireless/core.c文件会对wifi使用的cfg80211进行初始化。

        iw,hostapd,wpa_supplicant等通过Netlink 套接字和构建消息来与内核空间进行交互。当启动 AP 模式时,hostapd 会将请求发送到内核,内核接收并调用 cfg80211 的 start_ap等函数来处理 AP 启动。这个设计使得用户空间能够灵活地管理无线接入点,同时保持与内核的高效通信

static int __init cfg80211_init(void)
{
        int err;

        err = register_pernet_device(&cfg80211_pernet_ops); //注册 cfg80211 作为一个网络命名空间的设备。这使得每个网络命名空间都能够独立地管理自己的无线设备
        if (err)
                goto out_fail_pernet;

        err = wiphy_sysfs_init(); //此函数创建并初始化与 wiphy 相关的 SysFS 属性,允许用户通过 /sys 文件系统管理和查看无线设备的状态
        if (err)
                goto out_fail_sysfs;

        err = register_netdevice_notifier(&cfg80211_netdev_notifier);
        if (err)
                goto out_fail_notifier;

        err = nl80211_init(); //初始化 cfg80211 的 Netlink 接口,使得用户空间可以通过 Netlink 与内核进行无线配置和管理通信
        if (err)
                goto out_fail_nl80211;

        ieee80211_debugfs_dir = debugfs_create_dir("ieee80211", NULL); //创建一个调试文件系统目录 ieee80211,用于调试和特定信息输出

        err = regulatory_init(); //初始化监管域(Regulatory Domain)子系统,负责管理无线设备的频谱使用和符合法规的需求
        if (err)
                goto out_fail_reg;

        cfg80211_wq = create_singlethread_workqueue("cfg80211"); //工作队列提供了异步任务处理机制,使得无线配置任务可以在后台线程中执行,不阻塞主要系统线程。
        if (!cfg80211_wq) {
                err = -ENOMEM;
                goto out_fail_wq;
        }

        return 0;
}

cfg80211_netdev_notifier的实例

这个notifier,再被操作网络设备时,会执行cfg80211对应的操作:比如注册,启动,停止,注销等行为

static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
                                         unsigned long state,
                                         void *ndev)
{
        struct net_device *dev = ndev;
        struct wireless_dev *wdev = dev->ieee80211_ptr;
        struct cfg80211_registered_device *rdev;
        int ret;

        if (!wdev)
                return NOTIFY_DONE;

        rdev = wiphy_to_dev(wdev->wiphy);

        WARN_ON(wdev->iftype == NL80211_IFTYPE_UNSPECIFIED);

        switch (state) {
        case NETDEV_POST_INIT:
                SET_NETDEV_DEVTYPE(dev, &wiphy_type);
                break;
        case NETDEV_REGISTER:
                /*
                 * NB: cannot take rdev->mtx here because this may be
                 * called within code protected by it when interfaces
                 * are added with nl80211.
                 */
                mutex_init(&wdev->mtx);
                INIT_WORK(&wdev->cleanup_work, wdev_cleanup_work);
                INIT_LIST_HEAD(&wdev->event_list);
                spin_lock_init(&wdev->event_lock);
                INIT_LIST_HEAD(&wdev->mgmt_registrations);
                spin_lock_init(&wdev->mgmt_registrations_lock);

                mutex_lock(&rdev->devlist_mtx);
                wdev->identifier = ++rdev->wdev_id;
                list_add_rcu(&wdev->list, &rdev->wdev_list);
                rdev->devlist_generation++;
                /* can only change netns with wiphy */
                dev->features |= NETIF_F_NETNS_LOCAL;

                if (sysfs_create_link(&dev->dev.kobj, &rdev->wiphy.dev.kobj,
                                      "phy80211")) {
                        pr_err("failed to add phy80211 symlink to netdev!\n");
                }
                wdev->netdev = dev;
                wdev->sme_state = CFG80211_SME_IDLE;
                mutex_unlock(&rdev->devlist_mtx);
#ifdef CONFIG_CFG80211_WEXT
                wdev->wext.default_key = -1;
                wdev->wext.default_mgmt_key = -1;
                wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
#endif

                if (wdev->wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT)
                        wdev->ps = true;
                else
                        wdev->ps = false;
                /* allow mac80211 to determine the timeout */
                wdev->ps_timeout = -1;

                netdev_set_default_ethtool_ops(dev, &cfg80211_ethtool_ops);

                if ((wdev->iftype == NL80211_IFTYPE_STATION ||
                     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT ||
                     wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
                        dev->priv_flags |= IFF_DONT_BRIDGE;
                break;
        case NETDEV_GOING_DOWN:
                cfg80211_leave(rdev, wdev);
                break;
        case NETDEV_DOWN:
                cfg80211_update_iface_num(rdev, wdev->iftype, -1);
                dev_hold(dev);
                queue_work(cfg80211_wq, &wdev->cleanup_work);
                break;
        case NETDEV_UP:
                /*
                 * If we have a really quick DOWN/UP succession we may
                 * have this work still pending ... cancel it and see
                 * if it was pending, in which case we need to account
                 * for some of the work it would have done.
                 */
                if (cancel_work_sync(&wdev->cleanup_work)) {
                        mutex_lock(&rdev->devlist_mtx);
                        rdev->opencount--;
                        mutex_unlock(&rdev->devlist_mtx);
                        dev_put(dev);
                }
                cfg80211_update_iface_num(rdev, wdev->iftype, 1);
                cfg80211_lock_rdev(rdev);
                mutex_lock(&rdev->devlist_mtx);
                mutex_lock(&rdev->sched_scan_mtx);
                wdev_lock(wdev);
                switch (wdev->iftype) {
#ifdef CONFIG_CFG80211_WEXT
                case NL80211_IFTYPE_ADHOC:
                        cfg80211_ibss_wext_join(rdev, wdev);
                        break;
                case NL80211_IFTYPE_STATION:
                        cfg80211_mgd_wext_connect(rdev, wdev);
                        break;
#endif
#ifdef CONFIG_MAC80211_MESH
                case NL80211_IFTYPE_MESH_POINT:
                        {
                                /* backward compat code... */
                                struct mesh_setup setup;
                                memcpy(&setup, &default_mesh_setup,
                                                sizeof(setup));
                                 /* back compat only needed for mesh_id */
                                setup.mesh_id = wdev->ssid;
                                setup.mesh_id_len = wdev->mesh_id_up_len;
                                if (wdev->mesh_id_up_len)
                                        __cfg80211_join_mesh(rdev, dev,
                                                        &setup,
                                                        &default_mesh_config);
                                break;
                        }
#endif
                default:
                        break;
                }
                wdev_unlock(wdev);
                mutex_unlock(&rdev->sched_scan_mtx);
                rdev->opencount++;
                mutex_unlock(&rdev->devlist_mtx);
                cfg80211_unlock_rdev(rdev);

                /*
                 * Configure power management to the driver here so that its
                 * correctly set also after interface type changes etc.
                 */
                if ((wdev->iftype == NL80211_IFTYPE_STATION ||
                     wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
                    rdev->ops->set_power_mgmt)
                        if (rdev_set_power_mgmt(rdev, dev, wdev->ps,
                                                wdev->ps_timeout)) {
                                /* assume this means it's off */
                                wdev->ps = false;
                        }
                break;
        case NETDEV_UNREGISTER:
 /*
                 * NB: cannot take rdev->mtx here because this may be
                 * called within code protected by it when interfaces
                 * are removed with nl80211.
                 */
                mutex_lock(&rdev->devlist_mtx);
                /*
                 * It is possible to get NETDEV_UNREGISTER
                 * multiple times. To detect that, check
                 * that the interface is still on the list
                 * of registered interfaces, and only then
                 * remove and clean it up.
                 */
                if (!list_empty(&wdev->list)) {
                        sysfs_remove_link(&dev->dev.kobj, "phy80211");
                        list_del_rcu(&wdev->list);
                        rdev->devlist_generation++;
                        cfg80211_mlme_purge_registrations(wdev);
#ifdef CONFIG_CFG80211_WEXT
                        kfree(wdev->wext.keys);
#endif
                }
                mutex_unlock(&rdev->devlist_mtx);
                /*
                 * synchronise (so that we won't find this netdev
                 * from other code any more) and then clear the list
                 * head so that the above code can safely check for
                 * !list_empty() to avoid double-cleanup.
                 */
                synchronize_rcu();
                INIT_LIST_HEAD(&wdev->list);
                /*
                 * Ensure that all events have been processed and
                 * freed.
                 */
                cfg80211_process_wdev_events(wdev);
                break;
        case NETDEV_PRE_UP:
                if (!(wdev->wiphy->interface_modes & BIT(wdev->iftype)))
                        return notifier_from_errno(-EOPNOTSUPP);
                if (rfkill_blocked(rdev->rfkill))
                        return notifier_from_errno(-ERFKILL);
                mutex_lock(&rdev->devlist_mtx);
                ret = cfg80211_can_add_interface(rdev, wdev->iftype);
                mutex_unlock(&rdev->devlist_mtx);
                if (ret)
                        return notifier_from_errno(ret);
                break;
        }

        return NOTIFY_DONE;
}

static struct notifier_block cfg80211_netdev_notifier = {
        .notifier_call = cfg80211_netdev_notifier_call,
};

NETDEV_GOING_DOWN

比如调用这个通知链时的事件是down钓一个wlan0设备,就会调用cfg80211_leave的cfg80211_stop_ap

void cfg80211_leave(struct cfg80211_registered_device *rdev,
                   struct wireless_dev *wdev)
{
        struct net_device *dev = wdev->netdev;

        switch (wdev->iftype) {
        case NL80211_IFTYPE_ADHOC:
                cfg80211_leave_ibss(rdev, dev, true);
                break;
        case NL80211_IFTYPE_P2P_CLIENT:
        case NL80211_IFTYPE_STATION:
                mutex_lock(&rdev->sched_scan_mtx);
                __cfg80211_stop_sched_scan(rdev, false);
                mutex_unlock(&rdev->sched_scan_mtx);

                wdev_lock(wdev);
#ifdef CONFIG_CFG80211_WEXT
                kfree(wdev->wext.ie);
                wdev->wext.ie = NULL;
                wdev->wext.ie_len = 0;
                wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
#endif
                __cfg80211_disconnect(rdev, dev,
                                      WLAN_REASON_DEAUTH_LEAVING, true);
                wdev_unlock(wdev);
                break;
        case NL80211_IFTYPE_MESH_POINT:
                cfg80211_leave_mesh(rdev, dev);
                break;
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_P2P_GO:
                cfg80211_stop_ap(rdev, dev);
                break;
        default:
                break;
        }

        wdev->beacon_interval = 0;
}

会判断网络设备的ops有没有stop_ap成员

static int __cfg80211_stop_ap(struct cfg80211_registered_device *rdev,
			      struct net_device *dev)
{
	struct wireless_dev *wdev = dev->ieee80211_ptr;
	int err;

	ASSERT_WDEV_LOCK(wdev);

	if (!rdev->ops->stop_ap)
		return -EOPNOTSUPP;

	if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
	    dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
		return -EOPNOTSUPP;

	if (!wdev->beacon_interval)
		return -ENOENT;

	err = rdev_stop_ap(rdev, dev);
	if (!err) {
		wdev->beacon_interval = 0;
		wdev->channel = NULL;
		wdev->ssid_len = 0;
	}

	return err;
}

rdev_stop_ap会最终调用注册的设备的ops成员--stop_ap

static inline int rdev_stop_ap(struct cfg80211_registered_device *rdev,
			       struct net_device *dev)
{
	int ret;
	trace_rdev_stop_ap(&rdev->wiphy, dev);
	ret = rdev->ops->stop_ap(&rdev->wiphy, dev);
	trace_rdev_return_int(&rdev->wiphy, ret);
	return ret;
}

cfg80211_registered_device是怎么初始化的?

某个wifi驱动将具体实现的ops,赋值给要注册的cfg80211_registered_device的ops成员

void my_wifi_init(){
	wiphy = wiphy_new(&ssv_cfg80211_ops, sizeof(struct ssv_softc));
}


struct wiphy *wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)
{
	static int wiphy_counter;

	struct cfg80211_registered_device *rdev;
	int alloc_size;

	WARN_ON(ops->add_key && (!ops->del_key || !ops->set_default_key));
	WARN_ON(ops->auth && (!ops->assoc || !ops->deauth || !ops->disassoc));
	WARN_ON(ops->connect && !ops->disconnect);
	WARN_ON(ops->join_ibss && !ops->leave_ibss);
	WARN_ON(ops->add_virtual_intf && !ops->del_virtual_intf);
	WARN_ON(ops->add_station && !ops->del_station);
	WARN_ON(ops->add_mpath && !ops->del_mpath);
	WARN_ON(ops->join_mesh && !ops->leave_mesh);

	alloc_size = sizeof(*rdev) + sizeof_priv;

	rdev = kzalloc(alloc_size, GFP_KERNEL);
	if (!rdev)
		return NULL;

	rdev->ops = ops;

	mutex_lock(&cfg80211_mutex);

	rdev->wiphy_idx = wiphy_counter++;

	if (unlikely(rdev->wiphy_idx < 0)) {
		wiphy_counter--;
		mutex_unlock(&cfg80211_mutex);
		/* ugh, wrapped! */
		kfree(rdev);
		return NULL;
	}

	mutex_unlock(&cfg80211_mutex);

	/* give it a proper name */
	dev_set_name(&rdev->wiphy.dev, PHY_NAME "%d", rdev->wiphy_idx);

	mutex_init(&rdev->mtx);
	mutex_init(&rdev->devlist_mtx);
	mutex_init(&rdev->sched_scan_mtx);
	INIT_LIST_HEAD(&rdev->wdev_list);
	INIT_LIST_HEAD(&rdev->beacon_registrations);
	spin_lock_init(&rdev->beacon_registrations_lock);
	spin_lock_init(&rdev->bss_lock);
	INIT_LIST_HEAD(&rdev->bss_list);
	INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);
	INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results);
	INIT_DELAYED_WORK(&rdev->dfs_update_channels_wk,
			  cfg80211_dfs_channels_update_work);
#ifdef CONFIG_CFG80211_WEXT
	rdev->wiphy.wext = &cfg80211_wext_handler;
#endif

	device_initialize(&rdev->wiphy.dev);
	rdev->wiphy.dev.class = &ieee80211_class;
	rdev->wiphy.dev.platform_data = rdev;

#ifdef CONFIG_CFG80211_DEFAULT_PS
	rdev->wiphy.flags |= WIPHY_FLAG_PS_ON_BY_DEFAULT;
#endif

	wiphy_net_set(&rdev->wiphy, &init_net);

	rdev->rfkill_ops.set_block = cfg80211_rfkill_set_block;
	rdev->rfkill = rfkill_alloc(dev_name(&rdev->wiphy.dev),
				   &rdev->wiphy.dev, RFKILL_TYPE_WLAN,
				   &rdev->rfkill_ops, rdev);

	if (!rdev->rfkill) {
		kfree(rdev);
		return NULL;
	}

	INIT_WORK(&rdev->rfkill_sync, cfg80211_rfkill_sync_work);
	INIT_WORK(&rdev->conn_work, cfg80211_conn_work);
	INIT_WORK(&rdev->event_work, cfg80211_event_work);

	init_waitqueue_head(&rdev->dev_wait);

	/*
	 * Initialize wiphy parameters to IEEE 802.11 MIB default values.
	 * Fragmentation and RTS threshold are disabled by default with the
	 * special -1 value.
	 */
	rdev->wiphy.retry_short = 7;
	rdev->wiphy.retry_long = 4;
	rdev->wiphy.frag_threshold = (u32) -1;
	rdev->wiphy.rts_threshold = (u32) -1;
	rdev->wiphy.coverage_class = 0;

	rdev->wiphy.features = NL80211_FEATURE_SCAN_FLUSH;

	return &rdev->wiphy;
}
EXPORT_SYMBOL(wiphy_new);

wifi驱动会初始化一个cfg80211_ops的实例,并填充对应的操作函数

struct cfg80211_ops ssv_cfg80211_ops = {
    .add_virtual_intf = ssv_cfg80211_add_iface,
    .del_virtual_intf = ssv_cfg80211_del_iface,
    .change_virtual_intf = ssv_cfg80211_change_iface,
    .scan = ssv_cfg80211_scan,
    .connect = ssv_cfg80211_connect,
    .disconnect = ssv_cfg80211_disconnect,
    .add_key = ssv_cfg80211_add_key,
    .get_key = ssv_cfg80211_get_key,
    .del_key = ssv_cfg80211_del_key,
    .set_default_key = ssv_cfg80211_set_default_key,
    .set_default_mgmt_key = ssv_cfg80211_set_default_mgmt_key,
    .add_station = ssv_cfg80211_add_station,
    .del_station = ssv_cfg80211_del_station,
    .change_station = ssv_cfg80211_change_station,
    .mgmt_tx = ssv_cfg80211_mgmt_tx,
    .start_ap = ssv_cfg80211_start_ap,
    .change_beacon = ssv_cfg80211_change_beacon,
    .stop_ap = ssv_cfg80211_stop_ap,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 6, 0)
    .set_monitor_channel = ssv_cfg80211_set_monitor_channel,
#endif
    .probe_client = ssv_cfg80211_probe_client,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
    .update_mgmt_frame_registrations = ssv_cfg80211_update_mgmt_frame_registrations,
#else
    .mgmt_frame_register = ssv_cfg80211_mgmt_frame_register,
#endif
    .set_wiphy_params = ssv_cfg80211_set_wiphy_params,
    .set_txq_params = ssv_cfg80211_set_txq_params,
    .set_tx_power = ssv_cfg80211_set_tx_power,
    .remain_on_channel = ssv_cfg80211_remain_on_channel,
    .cancel_remain_on_channel = ssv_cfg80211_cancel_remain_on_channel,
    .dump_survey = ssv_cfg80211_dump_survey,
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)
    .set_channel = ssv_cfg80211_set_channel,
#endif
    .get_channel = ssv_cfg80211_get_channel,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)
    .update_ft_ies = ssv_cfg80211_update_ft_ies,
#endif
    .set_cqm_rssi_config = ssv_cfg80211_set_cqm_rssi_config,
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 12, 0)
    .channel_switch = ssv_cfg80211_channel_switch,
#endif
    .change_bss = ssv_cfg80211_change_bss,
    .get_station = ssv_cfg80211_get_station,
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 17, 0) || defined(CONFIG_SUPPORT_WPA3))
    .external_auth = ssv_cfg80211_external_auth,
#endif
};

比如stop_ap的实现如下

static int ssv_cfg80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
{
    struct ssv_softc *sc = wiphy_priv(wiphy);
    struct ssv_vif *ssv_vif = netdev_priv(dev);
    struct ssv_sta *sta;
    int mgmt_txq;
    struct ssv_sta *cur, *tmp;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
    struct station_del_parameters params;
    params.mac = NULL;
#endif

    SSV_LOG_DBG("[%s][%d]\n", __FUNCTION__, __LINE__);
    sc->is_stoping_apm=true;

    { // turn off traffic for use_monitor interface
        struct ssv_vif *tmp_vif = NULL;

        list_for_each_entry(tmp_vif, &sc->vifs, list) {
            if (tmp_vif->use_monitor) {
                netif_tx_stop_all_queues(tmp_vif->ndev);
                netif_carrier_off(tmp_vif->ndev);
                break;
            }
        }
    }

    netif_tx_stop_all_queues(dev);
    netif_carrier_off(dev);

    mgmt_txq = (0 == ssv_vif->drv_vif_index) ? SSV_SW_TXQ_ID_MNG0 : SSV_SW_TXQ_ID_MNG1;
    ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq);     //pause mgmt txq
    ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq);  //inactive mgmt txq

    list_for_each_entry_safe(cur, tmp, &ssv_vif->ap.sta_list, list) {
        ssv_drv_hci_tx_pause_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx);     //pause hci tx queue by sta
        ssv_drv_hci_tx_inactive_by_sta(sc->hci_priv, sc->hci_ops, cur->sta_idx);  //inactive hci tx queue by sta
    }

    msleep(200);
#if 0
    ssv_send_apm_stop_req(sc, ssv_vif);
    mutex_lock(&sc->cb_lock);
    ssv_chanctx_unlink(ssv_vif);
    mutex_unlock(&sc->cb_lock);
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
    ssv_vif->bchan_setting = false;
#endif

    /* delete any remaining STA*/
    while (!list_empty(&ssv_vif->ap.sta_list))
    {
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)
        ssv_cfg80211_del_station(wiphy, dev, &params);
#else
        ssv_cfg80211_del_station(wiphy, dev, NULL);
#endif
    }
#if 1
    //give more times to cousmer the all tx packets
    //msleep(100);

    ssv_send_apm_stop_req(sc, ssv_vif);
    mutex_lock(&sc->cb_lock);
    ssv_chanctx_unlink(ssv_vif);
    mutex_unlock(&sc->cb_lock);
#endif
    /* delete BC/MC STA */
    sta = &sc->sta_table[ssv_vif->ap.bcmc_index];
    ssv_del_bcn(&ssv_vif->ap.bcn);
    ssv_del_csa(ssv_vif);


    ssv_drv_hci_tx_active_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //active hci mng. queue
    ssv_drv_hci_tx_resume_by_sta(sc->hci_priv, sc->hci_ops, mgmt_txq); //resume hci mng. queue
    SSV_LOG_DBG(KERN_ERR"AP stop!!\r\n");
    sc->is_stoping_apm=false;
    return 0;
}

register_netdevice_notifier的使用

各网络模块的使用

主要是以通知链的形式,来对内核的各个模块。比如mac80211,ipv4/6,bridge,netfilter等模块也会调用register_netdevice_notifier注册对应函数,来响应网络设备的某种操作,以做出该模块需要做出的操作

int register_netdevice_notifier(struct notifier_block *nb)
{
	struct net_device *dev;
	struct net_device *last;
	struct net *net;
	int err;

	rtnl_lock();
	err = raw_notifier_chain_register(&netdev_chain, nb);
	if (err)
		goto unlock;
	if (dev_boot_phase)
		goto unlock;
	for_each_net(net) {
		for_each_netdev(net, dev) {
			err = nb->notifier_call(nb, NETDEV_REGISTER, dev);
			err = notifier_to_errno(err);
			if (err)
				goto rollback;

			if (!(dev->flags & IFF_UP))
				continue;

			nb->notifier_call(nb, NETDEV_UP, dev);
		}
	}

unlock:
	rtnl_unlock();
	return err;

rollback:
	last = dev;
	for_each_net(net) {
		for_each_netdev(net, dev) {
			if (dev == last)
				goto outroll;

			if (dev->flags & IFF_UP) {
				nb->notifier_call(nb, NETDEV_GOING_DOWN, dev);
				nb->notifier_call(nb, NETDEV_DOWN, dev);
			}
			nb->notifier_call(nb, NETDEV_UNREGISTER, dev);
		}
	}

outroll:
	raw_notifier_chain_unregister(&netdev_chain, nb);
	goto unlock;
}
EXPORT_SYMBOL(register_netdevice_notifier);

比如bridge在初始化的时候也会调用register_netdevice_notifier来在netdev_chain链上注册一个对事件响应的notifier

static int __init br_init(void)
{
	int err;

	BUILD_BUG_ON(sizeof(struct br_input_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));

	err = stp_proto_register(&br_stp_proto);
	if (err < 0) {
		pr_err("bridge: can't register sap for STP\n");
		return err;
	}

	err = br_fdb_init();
	if (err)
		goto err_out;

	err = register_pernet_subsys(&br_net_ops);
	if (err)
		goto err_out1;

	err = br_nf_core_init();
	if (err)
		goto err_out2;

	err = register_netdevice_notifier(&br_device_notifier);
	if (err)
		goto err_out3;

	err = register_switchdev_notifier(&br_switchdev_notifier);
	if (err)
		goto err_out4;

	err = br_netlink_init();
	if (err)
		goto err_out5;

	brioctl_set(br_ioctl_deviceless_stub);

#if IS_ENABLED(CONFIG_ATM_LANE)
	br_fdb_test_addr_hook = br_fdb_test_addr;
#endif

#if IS_MODULE(CONFIG_BRIDGE_NETFILTER)
	pr_info("bridge: filtering via arp/ip/ip6tables is no longer available "
		"by default. Update your scripts to load br_netfilter if you "
		"need this.\n");
#endif

	return 0;

err_out5:
	unregister_switchdev_notifier(&br_switchdev_notifier);
err_out4:
	unregister_netdevice_notifier(&br_device_notifier);
err_out3:
	br_nf_core_fini();
err_out2:
	unregister_pernet_subsys(&br_net_ops);
err_out1:
	br_fdb_fini();
err_out:
	stp_proto_unregister(&br_stp_proto);
	return err;
}

会对各种事件,比如网络设备的up,down,register等做出桥的对应操作

static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr)
{
	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
	struct net_bridge_port *p;
	struct net_bridge *br;
	bool changed_addr;
	int err;

	/* register of bridge completed, add sysfs entries */
	if ((dev->priv_flags & IFF_EBRIDGE) && event == NETDEV_REGISTER) {
		br_sysfs_addbr(dev);
		return NOTIFY_DONE;
	}

	/* not a port of a bridge */
	p = br_port_get_rtnl(dev);
	if (!p)
		return NOTIFY_DONE;

	br = p->br;

	switch (event) {
	case NETDEV_CHANGEMTU:
		dev_set_mtu(br->dev, br_min_mtu(br));
		break;

	case NETDEV_CHANGEADDR:
		spin_lock_bh(&br->lock);
		br_fdb_changeaddr(p, dev->dev_addr);
		changed_addr = br_stp_recalculate_bridge_id(br);
		spin_unlock_bh(&br->lock);

		if (changed_addr)
			call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);

		break;

	case NETDEV_CHANGE:
		br_port_carrier_check(p);
		break;

	case NETDEV_FEAT_CHANGE:
		netdev_update_features(br->dev);
		break;

	case NETDEV_DOWN:
		spin_lock_bh(&br->lock);
		if (br->dev->flags & IFF_UP)
			br_stp_disable_port(p);
		spin_unlock_bh(&br->lock);
		break;

	case NETDEV_UP:
		if (netif_running(br->dev) && netif_oper_up(dev)) {
			spin_lock_bh(&br->lock);
			br_stp_enable_port(p);
			spin_unlock_bh(&br->lock);
		}
		break;

	case NETDEV_UNREGISTER:
		br_del_if(br, dev);
		break;

	case NETDEV_CHANGENAME:
		err = br_sysfs_renameif(p);
		if (err)
			return notifier_from_errno(err);
		break;

	case NETDEV_PRE_TYPE_CHANGE:
		/* Forbid underlaying device to change its type. */
		return NOTIFY_BAD;

	case NETDEV_RESEND_IGMP:
		/* Propagate to master device */
		call_netdevice_notifiers(event, br->dev);
		break;
	}

	/* Events that may cause spanning tree to refresh */
	if (event == NETDEV_CHANGEADDR || event == NETDEV_UP ||
	    event == NETDEV_CHANGE || event == NETDEV_DOWN)
		br_ifinfo_notify(RTM_NEWLINK, p);

	return NOTIFY_DONE;
}

static struct notifier_block br_device_notifier = {
	.notifier_call = br_device_event
};

ifconfig wlan0 up怎么实现的?

ioctl

针对这个最常见的命令,是通过ioctl来实现的,核心在/net/core/dev_ioctl.c

int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)
{
	struct ifreq ifr;
	int ret;
	char *colon;

	/* One special case: SIOCGIFCONF takes ifconf argument
	   and requires shared lock, because it sleeps writing
	   to user space.
	 */

	if (cmd == SIOCGIFCONF) {
		rtnl_lock();
		ret = dev_ifconf(net, (char __user *) arg);
		rtnl_unlock();
		return ret;
	}
	if (cmd == SIOCGIFNAME)
		return dev_ifname(net, (struct ifreq __user *)arg);

	/*
	 * Take care of Wireless Extensions. Unfortunately struct iwreq
	 * isn't a proper subset of struct ifreq (it's 8 byte shorter)
	 * so we need to treat it specially, otherwise applications may
	 * fault if the struct they're passing happens to land at the
	 * end of a mapped page.
	 */
	if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
		struct iwreq iwr;

		if (copy_from_user(&iwr, arg, sizeof(iwr)))
			return -EFAULT;

		iwr.ifr_name[sizeof(iwr.ifr_name) - 1] = 0;

		return wext_handle_ioctl(net, &iwr, cmd, arg);
	}

	if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
		return -EFAULT;

	ifr.ifr_name[IFNAMSIZ-1] = 0;

	colon = strchr(ifr.ifr_name, ':');
	if (colon)
		*colon = 0;

	/*
	 *	See which interface the caller is talking about.
	 */

	switch (cmd) {
	/*
	 *	These ioctl calls:
	 *	- can be done by all.
	 *	- atomic and do not require locking.
	 *	- return a value
	 */
	case SIOCGIFFLAGS:
	case SIOCGIFMETRIC:
	case SIOCGIFMTU:
	case SIOCGIFHWADDR:
	case SIOCGIFSLAVE:
	case SIOCGIFMAP:
	case SIOCGIFINDEX:
	case SIOCGIFTXQLEN:
		dev_load(net, ifr.ifr_name);
		rcu_read_lock();
		ret = dev_ifsioc_locked(net, &ifr, cmd);
		rcu_read_unlock();
		if (!ret) {
			if (colon)
				*colon = ':';
			if (copy_to_user(arg, &ifr,
					 sizeof(struct ifreq)))
				ret = -EFAULT;
		}
		return ret;

	case SIOCETHTOOL:
		dev_load(net, ifr.ifr_name);
		rtnl_lock();
		ret = dev_ethtool(net, &ifr);
		rtnl_unlock();
		if (!ret) {
			if (colon)
				*colon = ':';
			if (copy_to_user(arg, &ifr,
					 sizeof(struct ifreq)))
				ret = -EFAULT;
		}
		return ret;

	/*
	 *	These ioctl calls:
	 *	- require superuser power.
	 *	- require strict serialization.
	 *	- return a value
	 */
	case SIOCGMIIPHY:
	case SIOCGMIIREG:
	case SIOCSIFNAME:
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
			return -EPERM;
		dev_load(net, ifr.ifr_name);
		rtnl_lock();
		ret = dev_ifsioc(net, &ifr, cmd);
		rtnl_unlock();
		if (!ret) {
			if (colon)
				*colon = ':';
			if (copy_to_user(arg, &ifr,
					 sizeof(struct ifreq)))
				ret = -EFAULT;
		}
		return ret;

	/*
	 *	These ioctl calls:
	 *	- require superuser power.
	 *	- require strict serialization.
	 *	- do not return a value
	 */
	case SIOCSIFMAP:
	case SIOCSIFTXQLEN:
		if (!capable(CAP_NET_ADMIN))
			return -EPERM;
		/* fall through */
	/*
	 *	These ioctl calls:
	 *	- require local superuser power.
	 *	- require strict serialization.
	 *	- do not return a value
	 */
	case SIOCSIFFLAGS:
	case SIOCSIFMETRIC:
	case SIOCSIFMTU:
	case SIOCSIFHWADDR:
	case SIOCSIFSLAVE:
	case SIOCADDMULTI:
	case SIOCDELMULTI:
	case SIOCSIFHWBROADCAST:
	case SIOCSMIIREG:
	case SIOCBONDENSLAVE:
	case SIOCBONDRELEASE:
	case SIOCBONDSETHWADDR:
	case SIOCBONDCHANGEACTIVE:
	case SIOCBRADDIF:
	case SIOCBRDELIF:
	case SIOCSHWTSTAMP:
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
			return -EPERM;
		/* fall through */
	case SIOCBONDSLAVEINFOQUERY:
	case SIOCBONDINFOQUERY:
		dev_load(net, ifr.ifr_name);
		rtnl_lock();
		ret = dev_ifsioc(net, &ifr, cmd);
		rtnl_unlock();
		return ret;

	case SIOCGIFMEM:
		/* Get the per device memory space. We can add this but
		 * currently do not support it */
	case SIOCSIFMEM:
		/* Set the per device memory buffer space.
		 * Not applicable in our case */
	case SIOCSIFLINK:
		return -ENOTTY;

	/*
	 *	Unknown or private ioctl.
	 */
	default:
		if (cmd == SIOCWANDEV ||
		    cmd == SIOCGHWTSTAMP ||
		    (cmd >= SIOCDEVPRIVATE &&
		     cmd <= SIOCDEVPRIVATE + 15)) {
			dev_load(net, ifr.ifr_name);
			rtnl_lock();
			ret = dev_ifsioc(net, &ifr, cmd);
			rtnl_unlock();
			if (!ret && copy_to_user(arg, &ifr,
						 sizeof(struct ifreq)))
				ret = -EFAULT;
			return ret;
		}
		return -ENOTTY;
	}
}

dev_ifsioc就是对ifocnfig命令做出响应的具体函数

static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
{
	int err;
	struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);
	const struct net_device_ops *ops;

	if (!dev)
		return -ENODEV;

	ops = dev->netdev_ops;

	switch (cmd) {
	case SIOCSIFFLAGS:	/* Set interface flags */
		return dev_change_flags(dev, ifr->ifr_flags);

	case SIOCSIFMETRIC:	/* Set the metric on the interface
				   (currently unused) */
		return -EOPNOTSUPP;

	case SIOCSIFMTU:	/* Set the MTU of a device */
		return dev_set_mtu(dev, ifr->ifr_mtu);

	case SIOCSIFHWADDR:
		if (dev->addr_len > sizeof(struct sockaddr))
			return -EINVAL;
		return dev_set_mac_address(dev, &ifr->ifr_hwaddr);

	case SIOCSIFHWBROADCAST:
		if (ifr->ifr_hwaddr.sa_family != dev->type)
			return -EINVAL;
		memcpy(dev->broadcast, ifr->ifr_hwaddr.sa_data,
		       min(sizeof(ifr->ifr_hwaddr.sa_data),
			   (size_t)dev->addr_len));
		call_netdevice_notifiers(NETDEV_CHANGEADDR, dev);
		return 0;

	case SIOCSIFMAP:
		if (ops->ndo_set_config) {
			if (!netif_device_present(dev))
				return -ENODEV;
			return ops->ndo_set_config(dev, &ifr->ifr_map);
		}
		return -EOPNOTSUPP;

	case SIOCADDMULTI:
		if (!ops->ndo_set_rx_mode ||
		    ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
			return -EINVAL;
		if (!netif_device_present(dev))
			return -ENODEV;
		return dev_mc_add_global(dev, ifr->ifr_hwaddr.sa_data);

	case SIOCDELMULTI:
		if (!ops->ndo_set_rx_mode ||
		    ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
			return -EINVAL;
		if (!netif_device_present(dev))
			return -ENODEV;
		return dev_mc_del_global(dev, ifr->ifr_hwaddr.sa_data);

	case SIOCSIFTXQLEN:
		if (ifr->ifr_qlen < 0)
			return -EINVAL;
		if (dev->tx_queue_len ^ ifr->ifr_qlen) {
			unsigned int orig_len = dev->tx_queue_len;

			dev->tx_queue_len = ifr->ifr_qlen;
			err = call_netdevice_notifiers(
					NETDEV_CHANGE_TX_QUEUE_LEN, dev);
			err = notifier_to_errno(err);
			if (err) {
				dev->tx_queue_len = orig_len;
				return err;
			}
		}
		return 0;

	case SIOCSIFNAME:
		ifr->ifr_newname[IFNAMSIZ-1] = '\0';
		return dev_change_name(dev, ifr->ifr_newname);

	case SIOCSHWTSTAMP:
		err = net_hwtstamp_validate(ifr);
		if (err)
			return err;
		/* fall through */

	/*
	 *	Unknown or private ioctl
	 */
	default:
		if ((cmd >= SIOCDEVPRIVATE &&
		    cmd <= SIOCDEVPRIVATE + 15) ||
		    cmd == SIOCBONDENSLAVE ||
		    cmd == SIOCBONDRELEASE ||
		    cmd == SIOCBONDSETHWADDR ||
		    cmd == SIOCBONDSLAVEINFOQUERY ||
		    cmd == SIOCBONDINFOQUERY ||
		    cmd == SIOCBONDCHANGEACTIVE ||
		    cmd == SIOCGMIIPHY ||
		    cmd == SIOCGMIIREG ||
		    cmd == SIOCSMIIREG ||
		    cmd == SIOCBRADDIF ||
		    cmd == SIOCBRDELIF ||
		    cmd == SIOCSHWTSTAMP ||
		    cmd == SIOCGHWTSTAMP ||
		    cmd == SIOCWANDEV) {
			err = -EOPNOTSUPP;
			if (ops->ndo_do_ioctl) {
				if (netif_device_present(dev))
					err = ops->ndo_do_ioctl(dev, ifr, cmd);
				else
					err = -ENODEV;
			}
		} else
			err = -EINVAL;

	}
	return err;
}

这个dev_change_flags就会检测对应的flag是否更改,以对更改的做出响应

int dev_change_flags(struct net_device *dev, unsigned int flags)
{
	int ret;
	unsigned int changes, old_flags = dev->flags, old_gflags = dev->gflags;

	ret = __dev_change_flags(dev, flags);
	if (ret < 0)
		return ret;

	changes = (old_flags ^ dev->flags) | (old_gflags ^ dev->gflags);
	__dev_notify_flags(dev, old_flags, changes);
	return ret;
}

call_netdevice_notifiers

最终通过通知连的形式去调用netdev_chain链的通知函数,cfg80211注册的有,最终是调用的是网络设备驱动提供的函数

void __dev_notify_flags(struct net_device *dev, unsigned int old_flags)
{
        unsigned int changes = dev->flags ^ old_flags;

        if (changes & IFF_UP) {
                if (dev->flags & IFF_UP)
                        call_netdevice_notifiers(NETDEV_UP, dev);
                else
                        call_netdevice_notifiers(NETDEV_DOWN, dev);
        }

        if (dev->flags & IFF_UP &&
            (changes & ~(IFF_UP | IFF_PROMISC | IFF_ALLMULTI | IFF_VOLATILE)))
                call_netdevice_notifiers(NETDEV_CHANGE, dev);
}

int call_netdevice_notifiers(unsigned long val, struct net_device *dev)
{
        ASSERT_RTNL();
        return raw_notifier_call_chain(&netdev_chain, val, dev);
}
EXPORT_SYMBOL(call_netdevice_notifiers);

wlan0的注册

通过register_netdevice来注册网络设备,比如wlan0;call_netdevice_notifiers(NETDEV_POST_INIT, dev)调用事件为初始化call_netdevice_notifiers(NETDEV_REGISTER, dev);调用事件为注册

int register_netdevice(struct net_device *dev)
{
	int ret;
	struct net *net = dev_net(dev);

	BUG_ON(dev_boot_phase);
	ASSERT_RTNL();

	might_sleep();

	/* When net_device's are persistent, this will be fatal. */
	BUG_ON(dev->reg_state != NETREG_UNINITIALIZED);
	BUG_ON(!net);

	spin_lock_init(&dev->addr_list_lock);
	netdev_set_addr_lockdep_class(dev);

	ret = dev_get_valid_name(net, dev, dev->name);
	if (ret < 0)
		goto out;

	/* Init, if this function is available */
	if (dev->netdev_ops->ndo_init) {
		ret = dev->netdev_ops->ndo_init(dev);
		if (ret) {
			if (ret > 0)
				ret = -EIO;
			goto out;
		}
	}

	if (((dev->hw_features | dev->features) &
	     NETIF_F_HW_VLAN_CTAG_FILTER) &&
	    (!dev->netdev_ops->ndo_vlan_rx_add_vid ||
	     !dev->netdev_ops->ndo_vlan_rx_kill_vid)) {
		netdev_WARN(dev, "Buggy VLAN acceleration in driver!\n");
		ret = -EINVAL;
		goto err_uninit;
	}

	ret = -EBUSY;
	if (!dev->ifindex)
		dev->ifindex = dev_new_index(net);
	else if (__dev_get_by_index(net, dev->ifindex))
		goto err_uninit;

	/* Transfer changeable features to wanted_features and enable
	 * software offloads (GSO and GRO).
	 */
	dev->hw_features |= NETIF_F_SOFT_FEATURES;
	dev->features |= NETIF_F_SOFT_FEATURES;

	if (dev->netdev_ops->ndo_udp_tunnel_add) {
		dev->features |= NETIF_F_RX_UDP_TUNNEL_PORT;
		dev->hw_features |= NETIF_F_RX_UDP_TUNNEL_PORT;
	}

	dev->wanted_features = dev->features & dev->hw_features;

	if (!(dev->flags & IFF_LOOPBACK))
		dev->hw_features |= NETIF_F_NOCACHE_COPY;

	/* If IPv4 TCP segmentation offload is supported we should also
	 * allow the device to enable segmenting the frame with the option
	 * of ignoring a static IP ID value.  This doesn't enable the
	 * feature itself but allows the user to enable it later.
	 */
	if (dev->hw_features & NETIF_F_TSO)
		dev->hw_features |= NETIF_F_TSO_MANGLEID;
	if (dev->vlan_features & NETIF_F_TSO)
		dev->vlan_features |= NETIF_F_TSO_MANGLEID;
	if (dev->mpls_features & NETIF_F_TSO)
		dev->mpls_features |= NETIF_F_TSO_MANGLEID;
	if (dev->hw_enc_features & NETIF_F_TSO)
		dev->hw_enc_features |= NETIF_F_TSO_MANGLEID;

	/* Make NETIF_F_HIGHDMA inheritable to VLAN devices.
	 */
	dev->vlan_features |= NETIF_F_HIGHDMA;

	/* Make NETIF_F_SG inheritable to tunnel devices.
	 */
	dev->hw_enc_features |= NETIF_F_SG | NETIF_F_GSO_PARTIAL;

	/* Make NETIF_F_SG inheritable to MPLS.
	 */
	dev->mpls_features |= NETIF_F_SG;

	ret = call_netdevice_notifiers(NETDEV_POST_INIT, dev); //调用初始化
	ret = notifier_to_errno(ret);
	if (ret)
		goto err_uninit;

	ret = netdev_register_kobject(dev);
	if (ret)
		goto err_uninit;
	dev->reg_state = NETREG_REGISTERED;

	__netdev_update_features(dev);

	/*
	 *	Default initial state at registry is that the
	 *	device is present.
	 */

	set_bit(__LINK_STATE_PRESENT, &dev->state);

	linkwatch_init_dev(dev);

	dev_init_scheduler(dev);
	dev_hold(dev);
	list_netdevice(dev);
	add_device_randomness(dev->dev_addr, dev->addr_len);

	/* If the device has permanent device address, driver should
	 * set dev_addr and also addr_assign_type should be set to
	 * NET_ADDR_PERM (default value).
	 */
	if (dev->addr_assign_type == NET_ADDR_PERM)
		memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);

	/* Notify protocols, that a new device appeared. */
	ret = call_netdevice_notifiers(NETDEV_REGISTER, dev); //调用注册
	ret = notifier_to_errno(ret);
	if (ret) {
		rollback_registered(dev);
		dev->reg_state = NETREG_UNREGISTERED;
	}
	/*
	 *	Prevent userspace races by waiting until the network
	 *	device is fully setup before sending notifications.
	 */
	if (!dev->rtnl_link_ops ||
	    dev->rtnl_link_state == RTNL_LINK_INITIALIZED)
		rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U, GFP_KERNEL);

out:
	return ret;

err_uninit:
	if (dev->netdev_ops->ndo_uninit)
		dev->netdev_ops->ndo_uninit(dev);
	if (dev->priv_destructor)
		dev->priv_destructor(dev);
	goto out;
}
EXPORT_SYMBOL(register_netdevice);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2181363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

遥感影像-实例分割数据集:iSAID 从切图到YOLO格式数据集制作详细介绍

背景介绍 开源数据集isaid标注包含实例分割&#xff0c;但是原始影像太大&#xff0c;很吃显存&#xff0c;一般显卡无法用原始影像直接训练&#xff0c;所以需要对影像进行裁剪&#xff0c;并生成对应的标签&#xff0c;因为想用yolo系列跑模型&#xff0c;所以将标签需要转为…

【设计模式-模板】

定义 模板方法模式是一种行为设计模式&#xff0c;它在一个方法中定义了一个算法的骨架&#xff0c;并将一些步骤延迟到子类中实现。通过这种方式&#xff0c;模板方法允许子类在不改变算法结构的情况下重新定义算法中的某些特定步骤。 UML图 组成角色 AbstractClass&#x…

Java 为什么使用 UTF-16 而不是更节省内存的 UTF-8?

Java 选择 UTF-16 编码而不是更节省内存的 UTF-8 这一决定&#xff0c;涉及多个层面的设计权衡&#xff0c;包括历史原因、虚拟机&#xff08;JVM&#xff09;实现的复杂度、性能和字符处理的一致性。要理解这个问题&#xff0c;我们需要从 Java 语言的设计初衷、JVM 的工作机制…

C++:笔试题

1.什么是虚函数&#xff1f;什么是纯虚函数&#xff1f; 虚函数是类中的一个成员函数&#xff0c;使用关键字virtual在函数名前声明。 虚函数主要目的是允许子类重写父类中的同名函数&#xff0c;从而实现多态性&#xff0c;并且子函数重写的是虚函数表中的函数。 当通过父类的…

七、添加攻击音效

一、添加动画事件 1、在动画事件中添加音效 2、添加音频组件 3、代码 public void PlayAttackSound() {AudioSource1.PlayOneShot(AudioClip1, SoundValue);//PlayOneShot播放一个音频剪辑&#xff08;AudioClip&#xff09;一次 }

Oracle 日志文件多路复用

多路复用 PRODCDB 数据库的所有日志组中的 redo log 文件&#xff0c;存放目录&#xff1a; /u01/app/oracle/oradata/MREDO 1.创建目录 mkdir -p /u01/app/oracle/oradata/MREDO 2.查看日志文件路径 select group#,member from v$logfile; 3.增加日志组文件 alter database a…

ElementUI el-tree 树组件 增加辅助线

需求 项目需求给elementUI的el-tree添加辅助线&#xff0c;并且不能使用其他插件&#xff0c;没办法只能该样式了。 效果 代码 html <template><div><el-scrollbar class"long-content"><el-tree node-key"id":data"deptTre…

《程序猿之Redis缓存实战 · 有序集合类型》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

OpenGL ES 索引缓冲区(4)

OpenGL ES 索引缓冲区(4) 简述 本节会介绍索引缓冲区&#xff0c;索引缓冲区和顶点缓冲区类似&#xff0c;也是显存上的一段内存&#xff0c;只不过上面的数据用处不同&#xff0c;索引缓冲区故名思义里面的数据是用于索引&#xff0c;主要作用是用于复用顶点缓冲区里的数据。…

Kd-tree介绍和使用

GeoHash原理介绍以及在redis中的应用-CSDN博客 这边文章中介绍了GeoHash编码原理以及它的一个应用——利用GeoHash编码可以建立一个索引&#xff0c;从而实现快速的空间搜索。今天&#xff0c;我们介绍一个常见的数据结构Kd-Tree&#xff0c;利用它也可以快速实现多位数据的搜索…

调用智谱AI,面试小助手Flask简单示例

文章目录 1.接入AI获取API密钥Python代码 2.小助手的实现流程3.Flask应用示例Python文件.pyindex.html运行Flask应用地址栏输入 http://localhost:5000/ 1.接入AI 获取API密钥 在智谱AI的官方网站上注册&#xff0c;右上角点击API密钥&#xff0c;新建并复制一个 API Key&…

掌握未来:产品经理学习AI大模型的重要性解析

前言 在AI大模型时代&#xff0c;技术的迅猛进步正在重塑各行各业的面貌。作为产品经理&#xff0c;我们不仅要紧跟时代步伐&#xff0c;更要深入探索与运用这一前沿技术。学习大模型等AI技术&#xff0c;不仅是为了理解其背后的工作原理和应用潜力&#xff0c;更是为了将智能…

天选思路怎能不会!小波变换+CNN完美融合,最新idea发了CV顶会!

今天给大家推荐一个涨点发顶会的好方向&#xff1a;小波变换CNN。这俩热点的结合可以轻松实现“11&#xff1e;2”的效果。 这是因为&#xff0c;一方面小波变换可以作为预处理步骤&#xff0c;提取出关键的局部特征&#xff0c;加速CNN收敛并提升性能&#xff1b;另一方面&am…

配置树莓派打开SSH服务

在树莓派终端中查看IP 在终端中输入命令来查看IP地址。最常用的命令是&#xff1a;hostname -I注意&#xff0c;这里的参数I是大写的&#xff0c;它表示查看本机上所有配置的IP地址&#xff08;包括IPv4和IPv6&#xff0c;如果有的话&#xff09;。如果你只需要查看IPv4地址&am…

Linux:磁盘管理

一、静态分区管理 静态的分区方法不可以动态的增加或减少分区的容量。 1、磁盘分区-fdisk 该命令是用于查看磁盘分区情况&#xff0c;和分区管理的命令 命令格式&#xff1a;fdisk [选项] 设备文件名常用命令&#xff1a; -h&#xff1a;查看分区信息 fdisk系统常用命令&…

19、网络安全合规复盘

数据来源&#xff1a;5.网络安全合规复盘_哔哩哔哩_bilibili

山大电力研发费用率远弱同行,先分红上亿再补流9000万?

《港湾商业观察》施子夫 8月9日&#xff0c;证监会网站披露深交所已向山东山大电力技术股份有限公司&#xff08;以下简称&#xff0c;山大电力&#xff09;发出第三轮审核问询函。据悉&#xff0c;2023年6月&#xff0c;山大电力递表深交所&#xff0c;保荐机构为兴业证券。 …

Linux中find命令详解

记录linux中find命令的详细用法。 文章目录 find命令简介基本语法常用选项-name-iname-type-size-mtime,-atime,-ctime-perm-user-group-delete-exec-printand or find --help find命令简介 find 是一个搜索目录树以查找一个文件或一组文件的程序。它遍历目录树并报告与用户规…

VMware虚拟机NAT模式配置

1、宿主机 2、宿主机网络适配器 VMnet8 ①要与虚拟机在同一网段 ②不能和宿主机同一网段 3、VMware 4、虚拟机

短视频剪辑工具有哪些?推荐4个简单好用的工具

短视频如今充斥着我们的生活&#xff0c;刷短视频已经成了很多人的生活必备。所以掌握短视频剪辑技能是一件很重要的事情&#xff0c;能够为视频创作者带来很多的流量。如果想要学习剪辑的话&#xff0c;可以先从选择一款合适的剪辑工具开始&#xff0c;这几款功能丰富的软件&a…