0%

文件系统

文件系统,就是来管理物理层面的硬盘,cd,u盘等存储设备,常见的spi,qspi,iic来读取flash和eeporm那些存储设备,这些空间的管理就需要文件系统了,这样可以方便用户知道剩余空间和打开文件。比如有NTFS、FATFS、FAT32等

文件io

我们也知道,我们在开发程序的时候,也是少不了open close write read这些文件io的,还有目录io,不过,为什么我可以使用这些api开发不出现问题呢?

不同的文件系统,它的分配、api、属性、特性都不一样,总不能开发的时候,还一个一个对着这些不同文件系统的api来进行处理吧?

换个思路理解,此时我有一个u盘,我插到linux开发板中,此时就会系统就会识别到一个存储设备,就会自动配置一个设备符,/dev/sda1,此时就可以mount挂载在特定的目录中,此时就可以读取了。此时,可以发现,不论u盘是什么属性什么系统,只要mount挂载上来就可以被读取,这就是虚拟文件系统帮的忙。

虚拟文件系统

一个系统不可能只有一个文件系统的,大家安装固态的时候,分区的时候,肯定就会选择一个文件系统进行格式化比如NTFS,这个时候,这个固态的文件系统就是NTFS了,同时还有一个大小、速率等属性就被系统读取到了,此时,只有把分区执行完毕才能在我的电脑中看到新增加的分盘。这和上面哪个挂载是一个原理。

有了这个虚拟文件系统,就可以极大的方便用户层的开发、使用和管理。本质上就是对多层的文件系统进行一次统一的封装,封装完毕之后,就可以方便上层开发了,所以,哪怕我只有一个2t的固态,我也可以分为1个E盘,一个F盘,至于这个E和F就是虚拟文件系统分出来的东西,实际上的空间就是这个2t的NTFS的固态啦。

总结

虚拟文件系统,只是一个对多个文件系统的api的一次封装,所有文件系统在接入系统的时候,都要主动去对接这个虚拟系统所提供的api,可以认为就是把函数对准虚拟文件系统的函数指针来,然后通过虚拟文件系统的封装,就可以提供给上层开发和管理啦。

编码

​ 什么是编码啊?我们都是adc有,采样量化编码(实际操作种还有滤波),它就是用来保存实际生活中的数据。人类是无法完美实现现实的情形,所以,模电转数电,一定就是经历了抽样,就是丢失,采样率越高就越接近实际但是,采样成功了,就需要输出出来,所以,此时就是依赖编码使用数字信号的方法存下来,然后输出出去。我们日常生活中,最常见的就是图片,视频,音频,字符,压缩.这个博客,只是提一下有什么常见编码,用到的时候再去学习。在这里总结一下,所谓的编码就是将信息或数据转换成特定格式或代码的过程,以便于存储、传输或处理。编码可以应用于各种数据类型,包括图片、视频、音频以及其他形式的数据,它们都是将一种形式的数据或信息转换为另一种形式,以实现特定的功能或目标。

图片格式编码

这个图片的编码模式就太多太多了,多的吓人

1
jpeg、png、gif、bmp、svg、pcx、tga、exif、fpx、psd、cdr、pcd、ufp、eps、ai、raw、WMF等
音频格式编码

音频编码:入门看这篇就够了丨音视频基础 - 知乎 (zhihu.com)

可以参考这个博客

1
2
3
4
5
6
7
8
主要目的是为了下面三个
1)时域冗余
2)频域冗余
3)听觉冗余

常见编码有
PCM 编码 脉冲编码调制(Pulse Code Modulation)
AAC 编码 Advanced Audio Coding
视频格式编码

视频编码格式全面解析 - 知乎 (zhihu.com)

可以参考这个博客

1
2
3
4
5
6
7
ffmpeg
H.264
VC-1是软件巨头微软力推
MPEG-7致力于视听数据信息编码的表达
新势力的WebM
MPEG-2和MPEG -1
MPEG -4
字符格式编码
1
2
3
4
5
Unicode 就是 UTF-8、UTF-16、UTF-32  (Unicode Transformation Format) 二进制格式来表示 Unicode 字符
BOM 就是byte-order mark 的缩写,是 "字节序标记" 的意思
ASCII
GB2312`、`GB18030 、GBK
常用也就是utf-8,GBK 、BG2312,有的时候不支持这个编码就需要使用api来进行编码切换了,这个在python很常见的
信号编码
1
2
3
4
5
6
极化码
卷积码
PCM
Polar码
LDPC码
Turbo码
校验编码
1
2
3
4
5
6
纠错编码
汉明码
里德所罗门编码
低密度奇偶校验码
涡轮码
CRC
加密编码
1
2
3
4
5
6
对称加密编码
非对称加密编码
数字签名
MD5、SHA 系列、HMAC 系列、RSA、AES、DES、3DES、RC4、Rabbit、SM 系列
恺撒密码、栅栏密码、猪圈密码、摩斯密码、培根密码、维吉尼亚密码、与佛论禅、当铺密码
那些密码本都算
压缩编码
1
2
哈夫曼编码
LZ编码 LZ77
总结

可能大家觉得这期博客很水,是因为我本人其实对这些编码确实是不熟的,但是如果实际开发中,连编码都不知道的话,那么是很费力,尤其是像方案和创新的时候,不是说记得多少,有多少知识就有用了,任何一件事情,只要学习百分之60或者有一个正确的概念,在实现的时候再去学习,这才是有用的,一直基于理论的学习是没有重要意义的。编码终究是协议来着,就算使用也是调用库,调用api,所以,我们只要知道这是什么编码,什么地方可以用到编码,可能用到什么编码,我有没有可能在工程中使用到或者切换编码。不会真想看着编码规则,手搓代码吧?那是最愚蠢的行为,花费巨量时间,干着已经成熟的事,别人甚至连优化都做好了,难道硬要用自己手搓的吗?

起因

今天复习了一下网络编程,里面就有一个然后复习一下多路复用。突然,我想到了一件事情,多路复用,肯定就要对存储了套接字的数据类型进行遍历,如链表数组二叉树等。但是遍历过程,就无法实现像tcp和udp那样堵塞等待了啊!那么怎么还能正常实现网络通讯呢!突然我想起来了,我刚学c语言的时候的烦恼。缓冲区!

缓冲区

顾名思义,就是作为数据缓冲,常见于输入和输出中,还记得刚学c语言的时候,printf scanf fput啥的,遇到\n清空缓冲区,刷新缓冲区等东西,都是的。当时就是,非常恼火,为啥要这个破缓冲区啊!现在,就明白,缓冲区是一个非常好的东西。

缓冲区基础作用

缓冲区,最基础的作用就是
输入方面,临时存放数据并告诉cpu来执行中断,防止cpu因为有高优先级任务导致数据丢失。
输出方面,快速连贯的发送,不需要来回执行发送,可以一步到位批量处理,减少硬件资源的消耗。
接收方面,甚至说还可以实现因为两者速率不对等,可以实现一个速率的缓冲,平衡速度差异。
比如两者都是9600波特率,但是硬件的时钟肯定是不可能都一致的。所以数据就可以全部存到缓冲区
等cpu过来拿就好了,哪怕多了一两帧也可以实现速率通信,注意不要太离谱了,而且接收端要采用定期拿走、中断空闲一次性接收拿走、轮询提取等方法。不然可能会导致通信堵塞卡死,数据丢失等诸多问题。

缓冲区系统层面作用

自然我们是离不开系统来分析的,其实缓冲区还有一点就是为了防止内核的多次调用了。我们的应用层一般是处于
用户状态,除了用户状态所有都是特权状态,此时用户状态是无法进行任何底层操作的,只有通过系统调用,软中
断,异常才能让cpu变为特权模式,此时才能去调用底层。
一般系统都是要防止,应用层随意调用底层,所以,容易出现很多段错误,总线错误等,就是为了保护底层,这也
是为了系统的稳定和文件系统的稳定,是非常合理的。所以应用层必须想要发送数据,那么就要使用系统调用,不
可能发一个字节,就调用一次系统调用,那么效率肯定是大打折扣的。同理接收也是这道理。
arm的特权一共有7种,如下图所示:

每次触发大量的接收,就会导致cpu被直接调度到特权模式,此时对于资源是一种极大的浪费,导致系统需要在两个状态反复切换,极大影响效率。所以引入缓冲区之后,输出可以一连串发送出去,这样进入内核的时候就少了,输入也是,可以选择空闲状态接收或者dma接收,这样就可以实现缓冲区收发,牺牲一些实时性,来换取效率和资源,是很赚的。

结语

因为有了缓冲区的存在,系统的资源和调度变得更好管理,效率和资源利用率也得到了提高,但是可不避免的损失了实时性。这也解决了很多因为cpu无法及时到来导致数据丢失问题。cpu毕竟是大家的。所以一开始的多路复用的问题也就是迎刃而解了

TCP/IP
UDP
HTTP
DNS
HTTPS
SNMP
STMP
SSH
SSL
POP
IMAP
MIME
TELNET
IGMP
TFTP
FTP
ICMP
DHCP
TLS
PPPoE
PPP
dhcp
static
PPPoA
3g(PPP over EV-DO, CDMA, UMTS or GRPS )
PPtP(Point-to-Point Tunneling Protocol)
IGRP
RIP
OSPF
EIGRP
EGP
BGP
IS-IS

起因

之前一直没搞懂,这两个的区别,甚至以为可以一起使用,哈哈哈,两个还是有很大的本质区别的。首先就是实时性了。还有成本,效率,使用场景都是不同的。

性质

都是基于差分信号来实现的,数据发送和传输,所以,它们有着一样的电气特性。但是,他们的数据和工作原理是完全不同的。最直接比较吧,CAN是全主机,MODBUS是一主多从。因为CAN有线与操作,所以,0电平就会被当成显性电平,从而获得话语权。显性电平和隐性电平也是can独有的。而MODBUS就是在485的技术上,增加一些对于帧的要求,对于时序的要求。相比起CAN来说,数据帧和查错能力完全不能比

MODBUS

Modbus协议是一种用于工业控制的网络通讯协议,可以片面的理解为,Modbus协议一种机器与机器之间进行数据、信息传递的一种格式规范。
Modbus协议还遵循主从协议,支持单主机,多从机,最多支持247个从机设备。并且,在同一个通信线路上只会有一个主机,所有的通讯过程全部由主机主动发起,从机接收到主机请求后,会对请求做出响应。从机不会主动进行数据的发送,从机之间也不会有通讯过程。
Modbus的通讯方式有:串行通讯方式、以太网通讯方式、串行-以太网转换方式、无线通讯方式

MODBUS确实可以用于网络tcp通信,不过这个我确实没用过,以后用到再补充。

这边就说一下,串行链路吧,就是一个一主多从的模式,主机发送数据给特定从机,从机接收到进行通信,有点iic的味道,一般是建立在485上面的。在iso中属于第七层的应用层,所以,它是可以做到,只要从设备也好些对应从处理,就可以通过485网络实现地址访问和处理了,这其实就是一个广播,对应从机接收到就相应罢了。

如果从机突然没了,主机也是会知道的,这个就看怎么处理了。也要知道一件事情,这个从机出问题是非常麻烦的,设置可能会破坏这个总线链路。

CAN

CAN,这个一看就是车载标配了,可以说,只要是车载就是CAN,基本没有modbus的事情,modbus都是工控那些为主,这是为什么呢?

因为CAN没有主从之分,只有优先级之分,所有人有情况都要立刻反馈到总线之中,比如有刹车信号了,立刻发送到总线之后,此时刹车在发送信号之前就应该执行了,不过是通知总线而已,此时一些其他的制动设备就跟着一起运作,上位应用也因此响应。

不同上面的modbus,它是一主多从,就导致了从机很多时候,是没有主动访问的权限,都是等待主机来一个个询问,这明显是不符合车载需要的,而且车载也确实需要优先级来做到一个先后问题,而且can更加严格,要求也更多,出现问题就多看错误码吧,这个没啥好说的,多调多试,看can手册就会了。而且一个从设备出问题,识别到多次错误还错就会把这个设备屏蔽,这个是一个非常厉害的功能。

一般分为标准帧和扩展帧,看实际的id够不够用吧,一般都是够的。

LIN

LIN(Local Interconnect Network)总线是基于UART/SCI(通用异步收发器/串行接口)的低成本串行通讯协议。其目标定位于车身网络模块节点间的低端通信,主要用于智能传感器和执行器的串行通信,而这正是CAN总线的带宽和功能所不要求的部分。

由于LIN网络在汽车中一般不独立存在,通常会与上层CAN网络相连,形成CAN-LIN网关节点。

什么是LIN总线?高速CAN、容错CAN、LIN总线有什么区别?-CSDN博客

所以就一个CAN-LIN节点挂载CAN总线上面,这个节点下面可以有多个从机,可以把这个节点当成一个主机来看待。

这些从机中有只有一个主机,其他都是从机,一个LIN网络最多可以连接16个节点,也就是15个从机,从机也是智能老实等待主机发送指令,不能越界主动申请发言。

LIN线在物理上是一个上拉到12V的集电极开漏总线,多个节点可以并联到同一根线上,与IIC的SDA类似,但其没有独立的时钟线,靠通信双方约定的波特率进行通信,与UART类似,但由于UART之处在于,其可以通过同步场进行时钟同步,使传输变得更可靠。这样与CAN总线的一对差分双绞线不同,其抗干扰能力大大减弱,通信速率最高只能20Kbps,而CAN可达1Mbps。

LIN线拓扑结构是主从结构的星型模型,所有传输都是master发起,slave不可以主动发起请求。这个其实与modbus更类似。也可以把slave看做消息的发布者,master看成消息的订阅者,想要获取某个变量值,就需要不断问询对应节点对应的报文。这个结构就决定了LIN线传输的消息不可能太多,以保证消息更新的实时性,通常一条LIN线上定义的消息要比一条CAN线上定义的消息少一个数量级。

汽车LIN总线与CAN的区别是什么?-EDN 电子技术设计 (ednchina.com)

使用场景

首先呢,CAN是多主机的,而且MODBUS只有一个主机,所以MODBUS同一时刻只能有一个设备在发送数据。但是很多时候,有些设备是高优先级的,他必须要得到发言,不可能等待其他人执行完毕,而且还要满足大家都发送,所以这个时候线与的作用就出来了,可以多主机仲裁,而且可靠,很多时候好像确实是CAN更好更更可靠,但是CAN更贵,配置也更难,工控的话MDOBUS就足够了。

总结

CAN和MODBUS,各有各自的优劣,主要还是看开发项目的用途。车载终究是涉及到人生安全,标准要更高一点,没办法的,成本也是高的,可靠那些CAN收发器价格就知道了,尤其是车载MCU价格搞得吓人,就是CAN的收发器多。MODBUS,就成本低,更适合工控啦。

CPU多级缓存

为什么需要多级缓存的存在呢?因为,内存太大了,不论是固态还是机械去读取都太慢了,但是cpu的频率又高的吓人,还有多级流水和,流水线设计方案等(比如结构冒险,数据冒险,时间冒险等方案,所以处理起来相当迅速)。所以,总不能让cpu在原地急得跺脚吧,这纯纯浪费资源呢,所以,就想到缓存操作。

cache高速缓存

这个,就是直接与cpu连接的一个缓存了,也可以说是速度最快的缓存了,他会提前把需要的数据从内存中提取出来,等待cpu调度和使用,这个非常好用,非常nice了,这就让cpu有的忙了,但是,问题因此来了,为什么cache快呢?就是因为它小啊,而且距离近。cpu处理任务是需要得到所有数据的,cache不可能存放所有数据的,这就来了一个cache命中率问题了。cache命中率越高,就说明了,cpu可以直接cache中得到数据,这是最好最快的。超出cache的就得去内存中找了。

一级缓存

上面的cache没捕获到,就到一级缓存中去找,这个就是离cpu第二远的了,因此内存也更大,执行的事情同上面差不多的。

比如 AMD一级数据缓存设计 AMD采用的一级缓存设计属于传统的“实数据读写缓存”设计。基于该架构的一级数据缓存主要用于存储CPU最先读取的数据;而更多的读取数据则分别存储在二级缓存和系统内存当中。
比如Intel 也有的一级缓存不存储数据,而是存储这些数据在二级缓存中的指令代码,这样就可以间接去找到来自二级缓存的数据,而且可以增加一级缓存的容量。

所以,不同的策略和设计方案,对于cpu来说,效果的好坏其实也是看实际情况的,不论是直接找数据,还是间接找指令跳转到数据,都是一种权衡之后的方案和策略,都是可行而且可靠的至于效果,只能实际测试才知道了。

二级缓存

二级缓存就是一级缓存的缓冲器:一级缓存制造成本很高因此它的容量有限,二级缓存的作用就是存储那些CPU处理时需要用到、一级缓存又无法存储的数据。

三级缓存

同样道理,三级缓存和内存可以看作是二级缓存的缓冲器,它们的容量递增,但单位制造成本却递减。

主存(硬盘)

到了这一步,说明上面都没成功的捕捉到cpu所需要的数据,只能从内存中读取了,这就效率非常低下了,这个是尽量要减少,但是避免不了的,当然这东西,我肯定设计不来,我也只会纸上谈兵。

局限性

这个就有必要说一说,为什么要谈这个呢?因为,我们写代码的时候,就知道,其实,我们的数据是一般都是在一块地方的,比如一个数组,比如一堆全局变量啥的,我们看他们的地址,其实都是连起来的,所以,执行一个应用和任务的时候,就可以猜出来这附近的地址可能都是需要的数据或者代码,所以,就有局限性,这也是符合编程和存储的。就像我们学习和使用mcu的时候,避免不了就是while死循环了,这极大可能就是反复用到同一个数据,所以,一个数据被使用,是不是就意味着它接着可以继续被使用呢?

1.时间局限性:如果某数据被访问过,在未来的一段时间内,我们可能还会使用到这个数据,所以缓存就提前把这个数据加载进来并留着,这样就可以让cpu调用的时候,快速去读取。

2.空间局限性:这个就是上面说,只要读取了一块地址,是不是就说明附近的地址都可能是应用和任务所需要的数据和内容呢?所以,宁可错杀一万,不可放过一人。

这些都是缓存读取的方案,自然还有更好的,都是看厂商的想法和设计了。

内存映射

这个就是把内存直接映射到缓存之中的方法,一般是直接映射到cache中的。

1.全相联映射:所有行均可用于存放主存任何一块数据

缺点:查找最慢,当cache块装满的时候,可能需要遍历所有的cache行

正常情况下我们并不限定位置,大家随便坐,先到先得。老师如果想检查一个同学的作业的话,也可以一个一个来找,即线性搜索,当然此举必然花费老师很多时间。另外一点需要强调,学生并不是老老实实坐在座位上的,不断会有学生进来或离开,也会有同学出去后又进来但前后的坐位并不相同的情况,这样的话“线性搜索”将不再可能。

2.直接映射:每个主存块只能放到某个固定的cache行,每个主存块所属的cahe行是 主存块号 % cache总行数

假设上面例子中的教室只有20个座位,编号为0-19,全校共有400名学生,学号分别为0-399。直接映射方式的求是座位不能随便选择,而是通过学号和所有座位数的余数决定,即学号为0、20、40、60…的同学只能坐在0号座位上,同样学号为1、21、41、61…的同学只能争1号座位,后来的同学会将正坐在位置上的同学挤出去,如果1号和21号同学都要来教室的话就会发生两人不断被挤出去的情况,这就是Cache的颠簸效应(Cache Thrashing)。

3.组组相联映射:首先要对所有的cache行进行分组,每个主存块只能放到固定的cache组,每个主存块所属的cahe组是 主存块号 % cache总组数,这个就是上面的组合拳头。

为了解决直接映射的颠簸效应,遂引入了组相联映射。假设学校共有5个系,每个系均有80名学生,那我们可以这样安排座位,一系的学生只能选择0/5/10/15这4个座,二系则是1/6/11/16,依次类推,如下图所示:每个系有4个座位,这4个座位是没有顺序的,即本系的学生随意坐,但全系的80名学生将争抢这4个座位,这种分为5组的映射方式就是4路组相连映射,4路的意思是每组中有4个座位,即4个CacheLine。

总结

存储内存,一般有段式,页式,段页式存储方法,同理缓存也有多个方案,没有绝对的方案,都是合适是最好的,一般都会使用折中方案比如段页式。虽然,我们百分之99的概率遇不上这些工作和研发,但是充分理解底层,选择合适的内存管理和分配是我们可以控制,理解cpu的调度方案和原理,其实,选择一个好的文件管理系统和内存分配方案,确实可以一定程度的提高速度,自然都是理论上,一切都是以实际为主,代码和人可以跑一个就非常鸟不起啊!

混合编程

什么是混合编程啊。就是不止一种语言的编程。额,这不等于废话。常见就是c调用c++,c++调用c。这就是最常见的混合编程,这是因为面向对象和面向过程都各有千秋,而且cpp的变量类型要求更大,所以更可靠点,c的话,小巧高效但是限制太少了,很容易越界,数据类型错误等。主要还是c使用面向过程的方法的话,是非常利于维护的

C调用CPP
1
2
3
4
5
6
7
8
9
10
11
12
//c调用cpp是不能直接使用cpp的类的,也不能直接使用cpp的函数,必须使用extern "C"{} //Cpp的头文件
class testCLASS
{
public:
static void funTest(void);
}
void outFun(void)
{
testCLASS::funTest();
}

extern "C" void outFun();
1
2
3
4
5
6
#include "testFUN.h" 

testCLASS::static void funTest(void) //Cpp文件
{
std::cout<<"OK"<<std::endl;
}
1
2
3
4
5
6
#include "testFUN.h" 	//Cpp的头文件

int main()
{
outFun();
}

这样就是一个最简单最简单的使用了,一般都是c来调用cpp,cpp很多情况都是可以直接调用c,只有部分,因为关键字
和数据结构体的问题等才会出现奇奇怪怪的bug。

C/CPP 调用Python

想不到吧,还能调用python呢,这个跟python版本有点关系,在python 3.12的官方手册中,有一个c扩展的功能,也就是通过调用Python.h来实现,对python函数的使用,那为什么可以实现这种情况呢。Python 本身就是一个C库。你所看到的可执行体python只不过是个stub。真正的python实体在动态链接库里实现,在Windows平台上,这个文件位于 %SystemRoot%\System32\python27.dll。

1
2
3
4
5
6
7
8
9
10
#include <Python.h>

int main(int argc, char *argv[])
{
Py_SetProgramName(argv[0]);
Py_Initialize();
PyRun_SimpleString("print 'Hello Python!'\n");
Py_Finalize();
return 0;
}

不过编译方法不是直接编译运行

1
2
3
4
5
#可以选择是devC++或者vs那种有命令行的ide
cl my_python.c -IC:\Python27\include C:\Python27\libs\python27.lib
#linux的话就是gcc操作
gcc my_python.c -o my_python -I/usr/include/python2.7/ -lpython2.7
#还可以使用Cython的方法,这个自行搜索吧

然后直接使用可执行文件即可

其实c和cpp调用python其实是多此一举的,性能又低,又不是我自己实现不了,cpp也是高级语言啊,cpp也有很多生态。
而且还有万能的指针,python就是看着舒服,简洁高效,模块化,效率和大小令人堪忧

Python调用C/CPP

直接使用Python的API,可以称之为最终解决方案。Cython, SWIG, SIP的接口文件转换后所生成的C/C++代码实际上都使用Python的API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 定义参数类型与函数名称
import ctypes
from ctypes.wintypes import UINT, DWORD

GetLastInputInfo = ctypes.windll.user32.GetLastInputInfo


class LASTINPUTINFO(ctypes.Structure):
_fields_ = [("cbSize", UINT), ("dwTime", DWORD)]


# 开始调用DLL导出的函数
def getLastInputTime_nt():
info = LASTINPUTINFO()
info.cbSize = ctypes.sizeof(info)
info.dwTime = 0
if not GetLastInputInfo(ctypes.byref(info)):
raise WindowsError("")
return info.dwTime

print(getLastInputTime_nt())

效率提升二十倍:Python和C的混合编程_python和c混合编程-CSDN博客

上面这个博客,是把cpp编译成dll和lib文件,然后让python来直接调用,这也算吧,说白,这就是库的思想,你就说是不是混合编程嘛。
而且它有关于c的语言的特殊的数据类型库,python本身是没有数据类型的,只有容器哦

1
import ctypes

rust调用c
c调用rust
结语

要说用的多,还是python调用cpp写好的库,这个多实在啊,方便简单。还有就是c调用cpp间接封装好的类接口。混合编程其实就是为了利用各种语言之间的便捷性来实现互通互补,你说c语言可以不可以搞定一切,那肯定可以啊,那时间呢?效率呢?难易程度呢?这些都是我们想要考虑的情况,尤其是python,它有非常庞大的生态,很多库python都是有的,难道c要一个一个写?还是直接使用python来实现混合编程?答案就一目了然了