2:中断中用work_queue 即 wait_queue 协作(简单工作中使用的示例)

1. 基本介绍

1.1:work_queue(相关接口在linux/kernel/workqueue.c和 linux/include/linux/workqueue.h中)

内核驱动中,一般的小型任务(work)都不会自己起一个线程来处理,而是扔到workqueu中处理。workqueue的主要工作就是用进程上下文来处理内核中大量的小任务(本质也是调用了)。

 工作队列是一种将工作推后执行的形式(中断下半部),它和tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。
      那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet.

所以workqueue的主要设计思想:一个是并行,多个work不要相互阻塞;另外一个是节省资源,多个work尽量共享资源(进程、调度、内存),不要造成系统过多的资源浪费。workqueue有如下多种优先级,常用system_wq(工作者)即可,每个工作者即一个线程.

工作放入工作队列之后,由管理此工作队列的工作者来执行这些work,通过alloc_workqueue或create_singlethread_workqueue来创建工作者线程,它最后调用kthread_create创建线程,其线程名为alloc_workqueue中指定的name,system_wq的线程名字即为"events" 其举例如下:

static int __init init_workqueues(void)  
{  
    unsigned int cpu;  
    int i;  
        ...  
    system_wq = alloc_workqueue("events", 0, 0);  
    system_long_wq = alloc_workqueue("events_long", 0, 0);  
    system_nrt_wq = alloc_workqueue("events_nrt", WQ_NON_REENTRANT, 0);  
    system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND,  
                        WQ_UNBOUND_MAX_ACTIVE);  
    system_freezable_wq = alloc_workqueue("events_freezable",  
                          WQ_FREEZABLE, 0);  
    BUG_ON(!system_wq || !system_long_wq || !system_nrt_wq ||  
           !system_unbound_wq || !system_freezable_wq);  
    return 0;  

 workqueue是对内核线程封装的用于处理各种工作项的一种处理方法, 由于处理对象是用链表拼接一个个工作项, 依次取出来处理, 然后从链表删除,就像一个队列排好队依次处理一样, 所以也称工作队列,所谓封装可以简单理解一个中转站, 一边指向“合适”的内核线程, 一边接受你丢过来的工作项, 用结构体 workqueue_srtuct表示, 而所谓工作项也是个结构体 --  work_struct, 里面有个成员指针, 指向你最终要实现的函数,

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
};

当然使用者在实现自己函数功能后可以直接调用,或者通过kthread_create()把函数当做新线程的主代码, 或者add_timer添加到一个定时器延时处理, 

那为何要弄个work_struct工作项先封装函数, 然后再丢到workqueue_srtuct处理呢? 这就看使用场景了, 如果是一个大函数, 处理事项比较多, 且需要重复处理, 可以单独开辟一个内核线程处理; 对延时敏感的可以用定时器; 

如果只是简单的一个函数功能,  且函数里面有延时动作的, 就适合放到工作队列来处理了, 毕竟定时器处理的函数是在中断上下文,不能delay或者引发进程切换的API,  而且开辟一个内核线程是耗时且耗费资源的, 一般用于函数需要while(1) 不断循环处理的,

不然处理一次函数后退出,线程又被销毁, 简直就是浪费!

将自己的工作项挂到已有的工作队列需要注意的是由于这些队列是共享的, 各个驱动都有可能将自己的工作项放到同个队列, 会导致队列的项拥挤, 当有些项写的代码耗时久或者调用delay()延时特别久, 你的项将会迟迟得不到执行! 

所以早期很多驱动开发人员都是自己创建workqueue, 添加自己的work。 在Linux-2.XXX时代, 创建workqueue时会创建属于workqueue自己的内核线程, 这些线程是“私有的”, 虽然是方便了驱动开发人员, 但每个驱动都“一言不合”就

创建workqueue导致太多线程, 严重占用系统资源和效率, 所以在Linux-3.XXX时代, 社区开发人员将workqueue和内核线程剥离! 内核会自己事先创建相应数量的线程(后面详解), 被所有驱动共享使用。  用户调用alloc_workqueue();

1.2 :wait_queue 

等待队列在linux内核中有着举足轻重的作用,很多linux驱动都或多或少涉及到了等待队列。因此,对于linux内核及驱动开发者来说,掌握等待队列是必须课之一。 Linux内核的等待队列是以双循环链表为基础数据结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head类型的域作为”连接件”。它通过一个双链表和把等待task的头,和等待的进程列表链接起来,在内核里面,等待队列是有很多用处的,尤其是在中断处理、进程同步、定时等场合。可以使用等待队列在实现阻塞进程的唤醒。它以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,同步对系统资源的访问等。

2. 使用示例

.h中:

    struct work_struct retreive_frame_work;

driver init中:

    INIT_WORK(&retreive_frame_work, retrieve_desc_task_callback);

    init_waitqueue_head(&frame_wq);

中断ISR中:
    {
        queue_work(system_wq, &retreive_frame_work);

    }

void retrieve_desc_task_callback(struct work_struct *work){
    add_desc(iav, 0); //生产frame_desc
    wake_up_interruptible_all(&frame_wq); //通知消费者,frame_desc已经生产完毕,可以去取了
}

消费由 retrieve_desc_task_callback 生产的 frame_desc(blocking 的方式) 

wait_event_interruptible(frame_wq,
            (desc = iav_find_frame_desc()));

参考链接如下:作者如需要删除请联系我,谢谢!

1. https://www.cnblogs.com/vedic/p/11069249.html

2. https://blog.youkuaiyun.com/morixinguan/article/details/69666642 ,包含诸如断tasklet, workqueue,softirq,中断下半部(bottom half)等概念

#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <Windows.h> #include <process.h> #include <conio.h> #include <thread> #include <atomic> #include <mutex> #include <string> #include <chrono> #include <vector> #include <queue> #include <condition_variable> // OpenCV #include <opencv2/opencv.hpp> // 海康 SDK #include "MvCameraControl.h" #pragma comment(lib, "MvCameraControl.lib") #pragma comment(lib, "opencv_world480.lib") // 修改为你的版本 // ==================== 配置参数 ==================== const char* const DESKTOP_TEST_DIR = "C:\\Users\\guolin12\\Desktop\\test"; const double RECORD_DURATION_SECONDS = 10.0; // 想录几秒? const size_t MAX_QUEUE_SIZE = 30; // 最大队列长度(防内存溢出) // ======================================== // 图像数据结构 // ======================================== struct ImageFrame { std::vector<uint8_t> data; MV_FRAME_OUT_INFO_EX info; std::chrono::steady_clock::time_point timestamp; // 用于调试时间间隔 }; // ======================================== // 全局变量 // ======================================== std::atomic<bool> g_bExit(false); std::atomic<bool> g_bIsRecording(false); std::mutex g_queueMutex; std::queue<ImageFrame> g_frameQueue; std::condition_variable g_cvQueue; int g_width = 0, g_height = 0; float g_fps = 25.0f; // 将从设备读取实际值 // 获取临时路径 std::string GetTempPathStr() { char path[MAX_PATH] = { 0 }; GetTempPathA(MAX_PATH, path); return std::string(path); } bool CreateDirectoryRecursive(const std::string& dir) { if (CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS) return true; size_t pos = dir.find_last_of("\\/"); if (pos != std::string::npos) { std::string parent = dir.substr(0, pos); if (CreateDirectoryRecursive(parent)) { return CreateDirectoryA(dir.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS; } } return false; } bool IsDirectoryWritable(const std::string& dir) { if (!CreateDirectoryRecursive(dir)) return false; std::string testFile = dir + "\\__test_write__.tmp"; FILE* f = nullptr; errno_t err = fopen_s(&f, testFile.c_str(), "w"); if (err == 0 && f) { fclose(f); DeleteFileA(testFile.c_str()); return true; } return false; } std::string GetOutputDirectory() { std::string target(DESKTOP_TEST_DIR); if (IsDirectoryWritable(target)) { printf("✅ Using output directory: %s\n", target.c_str()); return target; } else { printf("❌ Cannot write to %s\n", target.c_str()); std::string temp = GetTempPathStr(); if (IsDirectoryWritable(temp)) { printf("💡 Falling back to: %s\n", temp.c_str()); return temp; } printf("❌ All paths unwritable!\n"); return ""; } } void PrintDeviceInfo(MV_CC_DEVICE_INFO* pstDevInfo) { if (pstDevInfo->nTLayerType == MV_GIGE_DEVICE) { unsigned int ip = pstDevInfo->SpecialInfo.stGigEInfo.nCurrentIp; printf("IP: %d.%d.%d.%d\n", (ip >> 24) & 0xFF, (ip >> 16) & 0xFF, (ip >> 8) & 0xFF, ip & 0xFF); printf("Model: %s\n", pstDevInfo->SpecialInfo.stGigEInfo.chModelName); } else if (pstDevInfo->nTLayerType == MV_USB_DEVICE) { printf("Model: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chModelName); printf("Serial: %s\n", pstDevInfo->SpecialInfo.stUsb3VInfo.chSerialNumber); } } int SetMono8Format(void* handle) { int nRet = MV_CC_SetEnumValue(handle, "PixelFormat", PixelType_Gvsp_Mono8); if (nRet != MV_OK) { printf("❌ Failed to set PixelFormat to Mono8! [0x%x]\n", nRet); return nRet; } printf("✅ Pixel format set to Mono8.\n"); return MV_OK; } bool AlignImageSize(void* handle, int& width, int& height) { MVCC_INTVALUE stValue = { 0 }; if (MV_CC_GetIntValue(handle, "Width", &stValue) != MV_OK) { printf("❌ Failed to get Width\n"); return false; } width = (stValue.nCurValue / 32) * 32; MV_CC_SetIntValue(handle, "Width", width); printf("🔧 Set Width to %d\n", width); if (MV_CC_GetIntValue(handle, "Height", &stValue) != MV_OK) { printf("❌ Failed to get Height\n"); return false; } height = (stValue.nCurValue / 2) * 2; MV_CC_SetIntValue(handle, "Height", height); printf("🔧 Set Height to %d\n", height); return true; } // ✅ 修改:传入真实帧率作为输出 fps void RecordFromQueue(const std::string& filePath, int width, int height, int targetFrameCount, float outputFps) { printf("🎥 Recording started: %s\n", filePath.c_str()); printf("🎯 Target frames: %d, Output FPS: %.3f → Duration: %.2f sec\n", targetFrameCount, outputFps, targetFrameCount / outputFps); // ✅ 关键修改:使用真实帧率! cv::VideoWriter writer(filePath, cv::VideoWriter::fourcc('M', 'J', 'P', 'G'), outputFps, cv::Size(width, height), true); if (!writer.isOpened()) { printf("❌ Failed to open VideoWriter: %s\n", filePath.c_str()); g_bIsRecording = false; return; } int recorded = 0; auto start = std::chrono::steady_clock::now(); try { while (g_bIsRecording && !g_bExit && recorded < targetFrameCount) { std::unique_lock<std::mutex> lock(g_queueMutex); auto timeout = std::chrono::steady_clock::now() + std::chrono::seconds(3); // 更长等待时间 if (g_frameQueue.empty()) { if (g_cvQueue.wait_until(lock, timeout) == std::cv_status::timeout) { printf("📹 Timeout waiting for frame during recording.\n"); break; } } if (!g_frameQueue.empty()) { ImageFrame frame = std::move(g_frameQueue.front()); g_frameQueue.pop(); lock.unlock(); // 尽早释放锁 if (frame.info.enPixelType == PixelType_Gvsp_Mono8) { cv::Mat gray(frame.info.nHeight, frame.info.nWidth, CV_8UC1, frame.data.data()); cv::Mat bgr; cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR); writer.write(bgr); recorded++; } } } } catch (const std::exception& e) { printf("❌ Exception during recording: %s\n", e.what()); } catch (...) { printf("❌ Unknown error during recording.\n"); } writer.release(); auto durationMs = std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::steady_clock::now() - start).count(); printf("⏹️ Saved video: %s\n", filePath.c_str()); printf("📊 Wrote %d frames at %.3f FPS → Play time: %.2f sec\n", recorded, outputFps, recorded / outputFps); printf("⏱️ Actual process time: %.3f sec\n", durationMs / 1000.0); g_bIsRecording = false; } // 主工作线程:唯一调用 GetImageBuffer 的地方 unsigned int __stdcall WorkThread(void* pUser) { void* handle = pUser; cv::namedWindow("Live View", cv::WINDOW_AUTOSIZE); MV_FRAME_OUT stImageInfo = { 0 }; int nRet = MV_OK; // 初始化分辨率和帧率 AlignImageSize(handle, g_width, g_height); MVCC_FLOATVALUE stFloat = { 0 }; if (MV_CC_GetFloatValue(handle, "ResultingFrameRate", &stFloat) == MV_OK) { g_fps = stFloat.fCurValue; printf("🎯 Actual Camera FPS: %.3f\n", g_fps); } else { printf("⚠️ Could not get FPS, using default %.2f\n", g_fps); } std::string outputDir = GetOutputDirectory(); while (!g_bExit) { nRet = MV_CC_GetImageBuffer(handle, &stImageInfo, 1000); if (nRet == MV_OK) { MV_FRAME_OUT_INFO_EX* pInfo = &stImageInfo.stFrameInfo; // 👉 统一拷贝图像数据 std::vector<uint8_t> imgData(stImageInfo.pBufAddr, stImageInfo.pBufAddr + pInfo->nFrameLen); // 显示处理 cv::Mat gray(pInfo->nHeight, pInfo->nWidth, CV_8UC1, imgData.data()); cv::Mat bgr; cv::cvtColor(gray, bgr, cv::COLOR_GRAY2BGR); std::string status = g_bIsRecording ? "🔴 RECORDING..." : "✅ LIVE STREAM"; cv::putText(bgr, status, cv::Point(20, 40), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2); cv::imshow("Live View", bgr); int key = cv::waitKey(1); if (key == 27 || key == 'q' || key == 'Q') { g_bExit = true; } else if ((key == 'r' || key == 'R') && !g_bIsRecording) { if (outputDir.empty()) { printf("❌ No valid output directory!\n"); } else { g_bIsRecording = true; // ✅ 使用真实帧率计算目标帧数 int targetFrameCount = static_cast<int>(RECORD_DURATION_SECONDS * g_fps); printf("🎯 Start recording: %.3f FPS × %.1f sec = %d frames\n", g_fps, RECORD_DURATION_SECONDS, targetFrameCount); // 生成文件名(含毫秒) auto now = std::chrono::system_clock::now(); auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; std::time_t t = std::chrono::system_clock::to_time_t(now); struct tm tm; localtime_s(&tm, &t); char filePath[512]; sprintf_s(filePath, "%s\\rec_%04d%02d%02d_%02d%02d%02d_%03dms.avi", outputDir.c_str(), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count()); // 启动录像线程,传入真实帧率 try { std::thread t(RecordFromQueue, std::string(filePath), g_width, g_height, targetFrameCount, g_fps); // ✅ 把真实帧率传进去 t.detach(); } catch (const std::exception& e) { printf("❌ Failed to start thread: %s\n", e.what()); g_bIsRecording = false; } } } // 👉 如果正在录像,则入队 if (g_bIsRecording) { std::lock_guard<std::mutex> lock(g_queueMutex); // 控制队列大小,防止内存暴涨 while (g_frameQueue.size() >= MAX_QUEUE_SIZE) { g_frameQueue.pop(); } ImageFrame frame; frame.data = std::move(imgData); frame.info = *pInfo; frame.timestamp = std::chrono::steady_clock::now(); g_frameQueue.push(std::move(frame)); g_cvQueue.notify_one(); // 唤醒录像线程 } // ✅ 必须释放缓冲区 MV_CC_FreeImageBuffer(handle, &stImageInfo); } else { printf("⚠️ Timeout getting frame from camera.\n"); } } // 结束前通知所有等待线程退出 { std::lock_guard<std::mutex> lock(g_queueMutex); g_bIsRecording = false; } g_cvQueue.notify_all(); cv::destroyAllWindows(); return 0; } // 主函数 int main() { int nRet = MV_OK; void* handle = nullptr; HANDLE hThread = nullptr; do { nRet = MV_CC_Initialize(); if (nRet != MV_OK) { printf("❌ Initialize SDK failed! [0x%x]\n", nRet); break; } MV_CC_DEVICE_INFO_LIST stDevList = { 0 }; nRet = MV_CC_EnumDevices(MV_GIGE_DEVICE | MV_USB_DEVICE, &stDevList); if (nRet != MV_OK || stDevList.nDeviceNum == 0) { printf("❌ No devices found! [0x%x]\n", nRet); break; } printf("✅ Found %d device(s):\n", stDevList.nDeviceNum); for (unsigned int i = 0; i < stDevList.nDeviceNum; ++i) { printf(" [%d]: ", i); PrintDeviceInfo(stDevList.pDeviceInfo[i]); } unsigned int index = 0; printf("👉 Input camera index (0-%d): ", stDevList.nDeviceNum - 1); scanf_s("%d", &index); if (index >= stDevList.nDeviceNum) { printf("❌ Invalid index!\n"); break; } nRet = MV_CC_CreateHandle(&handle, stDevList.pDeviceInfo[index]); if (nRet != MV_OK) { printf("❌ CreateHandle failed! [0x%x]\n", nRet); break; } nRet = MV_CC_OpenDevice(handle); if (nRet != MV_OK) { printf("❌ OpenDevice failed! [0x%x]\n", nRet); break; } if (stDevList.pDeviceInfo[index]->nTLayerType == MV_GIGE_DEVICE) { int packetSize = MV_CC_GetOptimalPacketSize(handle); if (packetSize > 0) { MV_CC_SetIntValueEx(handle, "GevSCPSPacketSize", packetSize); } } MV_CC_SetEnumValue(handle, "TriggerMode", 0); if (SetMono8Format(handle) != MV_OK) break; nRet = MV_CC_StartGrabbing(handle); if (nRet != MV_OK) { printf("❌ StartGrabbing failed! [0x%x]\n", nRet); break; } unsigned int threadId = 0; hThread = (HANDLE)_beginthreadex(nullptr, 0, WorkThread, handle, 0, &threadId); if (!hThread) { printf("❌ Failed to create work thread!\n"); break; } printf("\n📌 Instructions:\n"); printf(" ➡ Press 'R' to record %.1f seconds video\n", RECORD_DURATION_SECONDS); printf(" ➡ Live preview will NOT freeze!\n"); printf(" ➡ Output FPS matches actual camera FPS\n"); printf(" ➡ File name includes milliseconds to avoid overwrite\n"); printf(" ➡ Press 'Q' or ESC to exit\n\n"); while (!g_bExit) { Sleep(100); } } while (false); // 清理资源 g_bExit = true; if (hThread) { WaitForSingleObject(hThread, 5000); CloseHandle(hThread); } if (handle) { MV_CC_StopGrabbing(handle); MV_CC_CloseDevice(handle); MV_CC_DestroyHandle(handle); } MV_CC_Finalize(); printf("👋 Program exited. Press any key to close.\n"); _getch(); return 0; } 无法打开#include <opencv2/opencv.hpp>
最新发布
11-19
<think>好的,我现在需要帮助用户解决他们在SystemC中使用sc_event_queue时遇到的错误:'class sc_core::sc_event_queue' has no member named 'get_event'。首先,我需要确定这个错误的原因。用户提供的示例代码中,在SC_THREAD使用了eq.get_event(),但错误提示说明sc_event_queue类没有这个成员函数。这可能是因为SystemC的版本问题。 我记得sc_event_queue是在SystemC的较新版本中引入的,可能是在2.3.3或更高版本。而get_event()方法是否存在呢?或者用户可能混淆了sc_event_queue和普通的sc_event的使用方法。需要查阅SystemC的文档或者头文件来确认sc_event_queue的成员函数。 查阅资料发现,sc_event_queue确实没有get_event()方法。正确的做法应该是直接使用事件队列本身作为等待的条件,因为sc_event_queue重载了隐式转换为const sc_event&的操作。所以,在wait()函数中可以直接传递事件队列对象,而不需要调用get_event()。 用户的代码中,SC_THREAD的wait(eq.get_event())应该改为wait(eq)。这样,当事件队列中有事件被通知时,线程会被触发。同时,num_available()方法可能也不存在,正确的应该是使用事件队列的其他方法,比如获取事件数量可能需要不同的方式,或者用户可能误解了该方法的用途。 接下来,我需要验证这一点。例如,在SystemC的官方文档中,sc_event_queue的成员函数包括notify(),可能还有其他的,但确实没有get_event()。正确的等待方式应该是等待事件队列本身的触发,而不是调用一个不存在的方法。 因此,用户需要修改他们的代码,将wait(eq.get_event())替换为wait(eq)。同时,检查num_available()是否存在,可能用户想要获取队列中的事件数量,但sc_event_queue可能没有这个方法,可能需要使用其他方式跟踪,或者可能用户记错了方法名。 另外,用户可能使用了旧版本的SystemC,其中sc_event_queue的实现不同,或者没有这个类。需要确认他们的SystemC版本是否为2.3.3或更高,并且是否正确包含了必要的头文件。 总结,错误的原因是错误地调用了get_event()方法,而正确的方法应该是直接等待事件队列对象。需要指导用户修改代码中的这一部分,并检查相关的其他方法是否正确。</think>这个错误表明您使用的SystemC版本中`sc_event_queue`类没有`get_event()`方法。以下是问题分析和修正方案: --- ### 错误原因 1. **版本兼容性问题** - `sc_event_queue`在SystemC 2.3.3+版本中引入,但早期实现可能缺少`get_event()`方法 - 部分编译器可能未正确实现该API 2. **语法误解** - `sc_event_queue`本身可直接作为`wait()`的参数,无需通过`get_event()`获取事件 --- ### 修正代码 ```cpp // 修改前(错误) wait(eq.get_event()); // 修改后(正确) wait(eq); // 直接使用事件队列对象 ``` --- ### 完整修正示例 ```cpp void thread_process() { while(true) { wait(eq); // 直接等待事件队列触发 ✅ cout << "线程响应 at " << sc_time_stamp() << " 队列深度: " << eq.num_available() << endl; wait(10, SC_NS); } } ``` --- ### 关键原理说明 1. **隐式转换机制** - `sc_event_queue`重载了`operator const sc_event&()` - 当调用`wait(eq)`时,会自动转换为`const sc_event&`类型 2. **队列行为保留** - 即使直接使用`wait(eq)`,仍保持FIFO事件处理特性 - 每个`notify()`调用都会生成一个独立事件 3. **版本验证方法** ```bash # 查看SystemC版本 dpkg -l | grep systemc # Linux brew info systemc # macOS ``` --- ### 扩展调试建议 1. **如果仍然报错** - 检查SystemC头文件路径:确认包含`<systemc.h>`而非旧版头文件 - 验证编译器标准:添加`-std=c++11`编译选项 2. **替代实现方案** ```cpp // 若版本确实不支持,改用sc_event_vector sc_vector<sc_event> eq{"eq", 5}; // 创建5个事件的向量 eq[count%5].notify(); // 轮流触发 ``` --- ### 正确输出示例 ```bash Method触发 at 300 ns 已发送队列事件 at 300 ns 线程响应 at 300 ns 队列深度:1 Method触发 at 400 ns Method触发 at 500 ns 已发送队列事件 at 500 ns 线程响应 at 510 ns 队列深度:1 ``` --- 通过以上修正,代码应能正常编译运行。如果问题持续,建议升级到SystemC 2.3.4最新版本。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值