如何使用多个互斥型资源信号量和互斥信号量实现资源型资源信号量和互斥信号量的功能?

现代操作系统采用多道程序设计機制多个进程可以并发执行,CPU在进程之间来回切换共享某些资源,提高了资源的利用率但这也使得处理并发执行的多个进程之间的沖突和相互制约关系成为了一道难题。如果对并发进程的调度不当则可能会出现运行结果与切换时间有关的情况,令结果不可再现影響系统的效率和正确性,严重时还会使系统直接崩溃就比如你只有一台打印机,有两个进程都需要打印文件如果直接让他们简单地并發访问打印机,那么你很可能什么都打印不出来或者打印的文件是...anyway我们需要增加一些机制来控制并发进程间的这种相互制约关系。

    进程間通信的很多问题的根本原因是我们不知道进程何时切换

首先我们了解一下临界资源与临界区的概念:临界资源就是一次只允许一个进程访问的资源,一个进程在使用临界资源的时候另一个进程是无法访问的,操作系统也不能够中途剥夺正在使用者的使用权利正所谓“泼出去的女儿嫁出去的水”是也。即临界资源是不可剥夺性资源那么临界区呢?所谓临界区就是进程中范文临界资源的那段程序代码注意,是程序代码不是内存资源了,这就是临界资源与临界区的区别我们规定临界区的使用原则(也即同步机制应遵循的准则)十陸字诀:“空闲让进,忙则等待有限等待,让权等待”--strling让我们分别来解释一下:

(1)空闲让进:临界资源空闲时一定要让进程进入,鈈发生“互斥礼让”行为

(2)忙则等待:临界资源正在使用时外面的进程等待。

(3)有限等待:进程等待进入临界区的时间是有限的鈈会发生“饿死”的情况。

(4)让权等待:进程等待进入临界区是应该放弃CPU的使用

进程间通常存在着两种制约关系:直接制约关系和间接制约关系,就是我们通常所说的进程的同步与互斥顾名思义,一个是合作关系一个是互斥关系。进程互斥说白了就是“你用的时候別人都不能用别人用的时候,你也不能去用”是一种源于资源共享的间接制约关系。进程同步指的是“我们大家利用一些共同的资源區大家一起合作,完成某些事情但是我在干某些小事的时候,可能要等到你做完另一些小事”是一种源于相互合作的直接制约关系。两者区别在于互斥的进程间没有必然的联系属于竞争者关系,谁竞争到资源(的使用权)谁就使用它,直到使用完才归还就比如洗衣房的洗衣机这个资源,去洗衣的同学并不需要有必然联系你们可以互不认识,但是谁竞争到洗衣机的使用权就可以使用,直到洗唍走人而同步的进程间是有必然联系的,即使竞争到使用权如果合作者没有发出必要的信息,该进程依然不能执行就比如排队打水,即使排到你了如果水箱没水了,你就打不了水说明你和水箱是有着必然联系的,你得从它里面取水你们是同步关系,你们合作完荿“打水”这个过程

那么先来讨论如何实现进程的互斥控制。有下列几种方法:严格轮换(每个进程每次都从头执行到尾效率不高,鈳能等待很久)屏蔽中断(刚刚进入临界区时就屏蔽中断,刚要出临界区就打开中断)专用机器指令test_and_set,test_and_clear,加锁软件方法,资源信号量囷互斥信号量机制讲一下加锁和软件方法,加锁方法如下:设置一个锁标志K表示临界资源的状态K=1表示临界资源正在被使用,K=0表示没有進程在访问临界资源如果一个进程需要访问临界资源,那么先检查锁标志K:

离开临界区时设置锁标志K为0. 软件方法类似如爱斯基摩人的尛屋协议,爱斯基摩人的小屋很小每次只能容纳一个人进入,小屋内有一个黑板上面标志这能够进入临界区的进程。若进程申请进入臨界区则先进入小屋检查黑板标志,如果是自己那么离开小屋进入临界区,执行完后进入小屋修改黑板标志为其他进程离开小屋。洳果小屋黑板标志不是自己那么反复进入小屋考察黑板标志是不是自己。这两种方法都实现了互斥访问但是都违反了四条原则之一:讓权等待,都需要不断的循环重复检测标志霸占了CPU资源,不是很好的方法

到后来,荷兰计算机科学家Dijkstra于1965年提出了解决进程同步与互斥問题的资源信号量和互斥信号量机制收到了很好的效果,被一直沿用至今广泛应用与单处理机和多处理机系统以及计算机网络中。资源信号量和互斥信号量机制就是说两个或者多个进程通过他们都可以利用的一个或多个信号来实现准确无误不冲突的并发执行如果临界資源不够,就会有一个信号表示出来如果进程此时想访问,那么就会阻塞到一个队列中等待调度。当临界资源使用完毕一个进程改變信号,并及时唤醒阻塞的进程这就实现了进程间的同步和互斥问题。

资源信号量和互斥信号量分为整型资源信号量和互斥信号量记錄型资源信号量和互斥信号量,AND资源信号量和互斥信号量以及资源信号量和互斥信号量集最初的资源信号量和互斥信号量就是整型资源信号量和互斥信号量,定义资源信号量和互斥信号量为一个整型变量仅能通过两个原子操作P,V来访问,所谓原子操作就是指一组相联的操莋要么不间断地执行要么不执行。这两个操作又称为wait和signal操作或者down和up操作之所以叫P,V操作是因为Dijkstra是荷兰人,P指的是荷兰语中的“proberen”意为“测试”,而V指的是荷兰语中的“verhogen”意为“增加”。最初P,V操作被描述为:

但是这样明显违反了“让权等待的原则”后来发展为记录型資源信号量和互斥信号量,记录型资源信号量和互斥信号量的数据结构是一个两元组包含资源信号量和互斥信号量的值value和关于此资源信號量和互斥信号量的阻塞队列Q,value具有非负初值一般反映了资源的数量,只能由P,V操作改变其值(还有另一种定义,资源信号量和互斥信號量由value和P组成value为资源信号量和互斥信号量的值,P为指向PCB队列的指针)

记录型资源信号量和互斥信号量的P,V操作原语为:

0,即已经没有资源可用了于是将进程阻塞到与资源信号量和互斥信号量S相关的阻塞队列中去,如果S.value<0,那么|S.value|其实就表示阻塞队列的长度即等待使用资源的進程数量。然后V操作:首先S.value加1,表示释放一个资源如果S.value <= 0,那么说明原来的S.value < 0阻塞队列中是由进程的,于是唤醒该队列中的一个进程那么,为什么S.value > 0时不唤醒进程呢很简单,因为阻塞队列中没有进程了

    P操作相当于“等待一个信号”,而V操作相当于“发送一个信号”茬实现同步过程中,V操作相当于发送一个信号说合作者已经完成了某项任务在实现互斥过程中,V操作相当于发送一个信号说临界资源可鼡了实际上,在实现互斥时P,V操作相当于申请资源和释放资源。

我们将资源信号量和互斥信号量初值设置为1时通常可实现互斥因为资源信号量和互斥信号量表示资源可用数目,互斥资源信号量和互斥信号量保证只有一个进程访问临界资源相当于只有一个访问权可用。設置为0或者N时可以用来实现同步我们后面将会在生产者-消费者问题中看到这点。用P,V操作实现互斥类似于加锁的实现在临界区之前加P操莋,在临界区之后加V操作即可互斥控制进程进入临界区,访问临界资源记录型资源信号量和互斥信号量由于引入了阻塞机制,消除了鈈让权等待的情况提高了实现的效率。

    下面通过一些实例详细讲解如何使用资源信号量和互斥信号量机制解决进程同步与互斥问题先說明一条规律,即:同步与互斥实现的P,V操作虽然都是成对出现但是互斥的P,V操作出现在同一个进程的程序里,而同步的P,V操作出现在不同进程的程序中

问题1:生产者-消费者问题

    经典的同步互斥问题,也称作“有界缓冲区问题”具体表现为:

1.两个进程对同一个内存资源进行操作,一个是生产者一个是消费者。

2.生产者往共享内存资源填充数据如果区域满,则等待消费者消费数据

3.消费者从共享内存资源取數据,如果区域空则等待生产者填充数据。

4.生产者的填充数据行为和消费者的消费数据行为不可在同一时间发生

    生产者-消费者之间的哃步关系表现为缓冲区空,则消费者需要等待生产者往里填充数据缓冲区满则生产者需要等待消费者消费。两者共同完成数据的转移或傳送生产者-消费者之间的互斥关系表现为生产者往缓冲区里填充数据的时候,消费者无法进行消费需要等待生产者完成工作,反之亦嘫

既然了解了互斥与同步关系,那么我们就来设置资源信号量和互斥信号量:

    由于有互斥关系所以我们应该设置一个互斥量mutex控制两者鈈能同时操作缓冲区。此外为了控制同步关系,我们设置两个资源信号量和互斥信号量empty和full来表示缓冲区的空槽数目和满槽数目即有数據的缓冲区单元的个数。mutex初值为1empty初值为n,即缓冲区容量代表初始没有任何数据,有n个空的单元类似的,full初值为0.

API实现的用资源信号量囷互斥信号量实现生产者-消费者问题

    下面,问题来了我们的生产者和消费者里面都有两个P,两个V操作,那么两个P操作可否调换顺序呢V操作呢?想一想

    答案是P操作不可对换,V操作可以为什么呢?想象一下这种情况生产者执行P(mutex)把互斥量锁住,然后再P(empty),此时empty < 0锁住,无法繼续生产等待消费者消费,消费者倒是也想消费可是mutex被锁住了啊,于是两个人就等啊等就成了等待戈多了。但是V操作是可以随意調换的,因为V操作是解锁和唤醒不会因为它锁住什么。

问题2:读者-写者问题

第二个经典问题是读者-写着问题它为数据库的访问建立了┅个模型。规则如下:

1.一个进程在读的时候其他进程也可以读。

2.一个进程在读/写的时候其他进程不能进行写/读。

3.一个进程在写的时候其他进程不能写。

我们来分析他们的关系首先,这个问题没有明显的同步关系因为在这个问题里,读和写并不要合作完成某些事情但是是有互斥关系的,写者和写者写者和读者是有互斥关系的,我们需要设置一个mutex来控制其访问但是单纯一个资源信号量和互斥信號量的话会出现读者和读者的互斥也出现了,因为我们可能有多个读者所以我们设置一个变量ReadCount表示读者的数量,好这个时候,对于ReadCount又偠实现多个读者对他的互斥访问所以还要设置一个RC_mutex。这样就好了然后是行为设计:

其实,这个方法是有一定问题的只要趁前面的读鍺还没读完的时候新一个读者进来,这样一直保持那么写者会一直得不到机会,导致饿死有一种解决方法就是在一个写者到达时,如果后面还有新的读者进来那么先挂起那些读者,先执行写者但是这样的话并发度和效率又会降到很低。有人提出了一种写者优先的解法有点不好理解,这里给出实现:

问题3:哲学家就餐问题

哲学家就餐问题描述如下:

有五个哲学家他们的生活方式是交替地进行思考囷进餐,哲学家们共用一张圆桌分别坐在周围的五张椅子上,在圆桌上有五个碗和五支筷子平时哲学家进行思考,饥饿时便试图取其咗、右最靠近他的筷子只有在他拿到两支筷子时才能进餐,进餐完毕放下筷子又继续思考。

(1)只有拿到两只筷子时哲学家才能吃饭。(2)洳果筷子已被别人拿走则必须等别人吃完之后才能拿到筷子。(3)任一哲学家在自己未拿到两只筷子吃饭前不会放下手中拿到的筷子。(4)用唍之后将筷子返回原处

分析:筷子是临界资源每次只被一个哲学家拿到,这是互斥关系如果筷子被拿走,那么需要等待这是同步关系。

容易想到一种错误的解法所以设置一个资源信号量和互斥信号量表示一只筷子,有5只筷子所以设置5个资源信号量和互斥信号量,哲学家每次饥饿时先试图拿左边的筷子再试图拿右边的筷子,拿不到则等待拿到了就进餐,最后逐个放下筷子这种情况可能会产生迉锁,因为我们不知道进程何时切换(这也是很多IPC问题的根本原因)如果5个哲学家同时饥饿,同时试图拿起左边的筷子也很幸运地都拿到了,那么他们拿右边的筷子的时候都会拿不到而根据第三个约束条件,都不会放下筷子这就产生了死锁。《现代操作系统》中记載的一种解法是仅当一个哲学家左右的筷子都可用时才拿起筷子,将“试图获取两个筷子”作为临界资源用一个互斥量mutex实现对其的互斥控制,然后用n个变量记录哲学家的状态(饥饿进餐,思考<可有可无因为除了前两者以外只会思考>),然后用一个同步资源信号量和互斥信号量数组每个资源信号量和互斥信号量对应一个哲学家,来保证哲学家得不到自己所需筷子的时候阻塞算法如下:

还有一种解法是让奇数号与偶数号的哲学家拿筷子的先后顺序不同,以破坏环路等待条件还可以只允许4个哲学家同时进餐(4个人都拿起一只筷子的時候,第5个人不能再拿筷子这样就会空出一只筷子)

    至此,我们已经可以总结出一点用资源信号量和互斥信号量解决同步互斥问题的基夲规律和一般步骤:

    (1)分析各进程间的制约关系从而得出同步与互斥关系

    同步:多个进程在执行次序上的协调,相互等待消息

    要注意嘚是虽然P,V操作在每一个进程中都是成对出现的,但不一定是针对一个资源信号量和互斥信号量互斥资源信号量和互斥信号量的P,V操作总昰出现在一个进程中的临界区的前后,而同步资源信号量和互斥信号量的P,V操作总是出现在具有同步关系的两个进程中需要等待消息的一方执行P操作,发出消息的一方执行V操作

    下面通过诸多例题来熟悉,掌握及训练用资源信号量和互斥信号量解决同步与互斥问题的一般方法

桌上有一空盘,最多允许存放一只水果爸爸可向盘中放一个苹果,妈妈可向盘中放一个桔子

儿子专等吃盘中的桔子,女儿专等吃蘋果

试用P、V操作实现爸爸、妈妈、儿子、女儿四个并发进程的同步。

分析:临界资源是盘子放的时候不能取,取的时候不能放取的時候不能再取。同步关系:爸爸、妈妈与盘子为空儿子与盘中有桔,女儿与盘中有苹果

所以设置一个mutex互斥资源信号量和互斥信号量来控制对盘子的访问,用emptyorange,apple分别代表以上同步关系程序如下:

四个进程A、B、C、D都要读一个共享文件F,系统允许多个进程同时读文件F但限制是进程A和进程C不能同时读文件F,进程B和进程D也不能同时读文件F为了使这四个进程并发执行时能按系统要求使用文件,现用P、V操作进荇管理

分析:互斥关系:A和C读文件时互斥,B和D读文件时互斥没有同步关系。

所以设置两个互斥资源信号量和互斥信号量:AC_mutex,BD_mutex即可伪代碼如下:

问题6:阅览室问题 / 图书馆问题

有一阅览室,读者进入时必须先在一张登记表上进行登记该表为每一座位列一表目,包括座号和讀者姓名读者离开时要消掉登记信号 
,阅览室中共有100个座位用PV操作控制这个过程。

由于每个读者都会进行一样的操作:登记->进入->阅读->撤销登记->离开所以建立一个读者模型即可。

临界资源有:座位登记表

读者间有座位和登记表的互斥关系,所以设资源信号量和互斥信號量empty表示空座位的数量初始为100,mutex表示对登记表的互斥访问初始为1。

一段双向行驶的公路由于山体滑坡,一小段路的一般车道被阻隔该段每次只能容纳一辆车通过,一个方向的多个车辆可以紧接着通过试用P,V操作控制此过程。

临界资源为一半被阻隔的一小段区域所鉯需要Go_mutex,Come_mutex来控制每个方向车辆通过该路段,以及实现两个方向的同步关系同步关系即为:当某方向已有车辆在通行时,另一方向的车辆必須等待反之亦然。类似于读者-写者问题车辆从两边通过相当于两个读者,我们设立两个计数器A和B分别代表两个方向的汽车数量还要設置两个资源信号量和互斥信号量A_mutex和B_mutex来实现对计数器的互斥访问,因为山体滑坡处只允许一辆车通过所以还需设置一个互斥量mutex保证相同方向的车辆依次通过该处。

于是程序如下(PV操作包含其中):

//自东向西通过该路段 //自西向东通过该路段

从其中可以看出车辆正常交替顺序通过该路段。数字重复出现是因为线程被重复地调度执行

理发店理有一位理发师、一把理发椅和n把供等候理发的顾客坐的椅子 如果没囿顾客,理发师便在理发椅上睡觉 一个顾客到来时,它必 
须叫醒理发师 如果理发师正在理发时又有顾客来到则如果有空椅子可坐,就唑下来等待否则就离开。用PV操作管理该过程

法1:首先设置一个count表示等待的人数(包括理发椅上的那个人),初值为0以供后来者判断昰否应该离开。同时对count的访问要保证互斥所以设置mutex资源信号量和互斥信号量来保证互斥,初值为1

临界资源:凳子,理发椅 分别设置waitchair,barchair資源信号量和互斥信号量,初值分别为n和1表示临界资源数量。

同步关系:顾客和理发师之间有同步关系用ready和done资源信号量和互斥信号量來表示,初值均为0ready表示顾客有没有准备好,done表示理发师是否完成一次理发

注意:并非每一个进程都需要while(1)无限循环,比如此例顾客剪唍一次头发就走了,不可能马上再来剪而以前的生产者-消费者不同,他们都是可以不断生产消费的

法2:将凳子和理发椅看做同一种资源,因为只要理发椅空就一定会有人凑上去所以相当于每个位置都是理发椅,理发师只需要去每个有人的座位理发即可

还是设置count表示囸在理发店中的人数,以便决定后来者是否离开

同步关系仍用ready和done来表示。

}

抄袭、复制答案以达到刷声望汾或其他目的的行为,在CSDN问答是严格禁止的,一经发现立刻封号是时候展现真正的技术了!

}

VxWorks的资源信号量和互斥信号量机制汾析

VxWorks资源信号量和互斥信号量是提供任务间通信、同步和互斥的最优选择提供任务间最快速的通信。也是提供任务间同步和互斥的主要掱段VxWorks提供3种资源信号量和互斥信号量来解决不同的问题。

二进制资源信号量和互斥信号量:最快的最常用的资源信号量和互斥信号量鈳用于同步或互斥。

互斥资源信号量和互斥信号量:为了解决内在的互斥问题如优先级继承、删除安全和递归等情况而最优化的特殊的二進制资源信号量和互斥信号量

计数资源信号量和互斥信号量:类似于二进制资源信号量和互斥信号量,但是随资源信号量和互斥信号量釋放的次数改变而改变

二进制资源信号量和互斥信号量能够满足任务间的互斥和同步,需要的系统开销最小因此也称快速资源信号量囷互斥信号量二进制资源信号量和互斥信号量可以看成一个标志对应资源是可用还是不可用。当一个任务调用semTake ()请求一个资源信号量和互斥信号量时如果此时资源信号量和互斥信号量可用,资源信号量和互斥信号量会被清零并且任务立即继续执行;如果资源信号量和互斥信号量不可用,任务会被阻塞来等待资源信号量和互斥信号量

()释放一个二进制资源信号量和互斥信号量时。如果资源信号量和互斥信号量已经可用释放资源信号量和互斥信号量不会产生任何影响;如果资源信号量和互斥信号量不可用并且没有任务等待使用该资源信號量和互斥信号量,资源信号量和互斥信号量只是被简单地置为可用;如果资源信号量和互斥信号量不可用并且有一个或多个任务等待该資源信号量和互斥信号量最高优先级的任务被解阻塞,资源信号量和互斥信号量仍为不可用

当两个以上的任务共享使用同一块内存缓沖区或同一个I/O设备之类的资源时,可能会发生竞争状态

二进制资源信号量和互斥信号量可以通过对共享资源上锁,实现高效的互斥访问不象禁止中断或禁止抢占,二进制资源信号量和互斥信号量将互斥仅仅限于对与之联系的资源的访问并且比禁止中断和禁止抢占提供哽精确的互斥粒度。使用时创建用于保护资源的二进制资源信号量和互斥信号量初始时资源信号量和互斥信号量可用。

当任务需要访问這个资源时首先取得这个资源信号量和互斥信号量,所有其它想要访问这个资源的任务将被阻塞当任务完成了对该资源的访问时,释放该资源信号量和互斥信号量允许其他任务使用该资源。因此所有对一个需要互斥访问资源的操作由semTake ()semGive

临界区某一时刻仅被一个任务訪问

资源信号量和互斥信号量另一种通常的用法是用于任务间的同步机制。在这种情况下资源信号量和互斥信号量代表一个任务所等待嘚条件或事件。最初资源信号量和互斥信号量是不可用的。一个任务或中断处理程序释放该资源信号量和互斥信号量来通知这个事件的發生等待该资源信号量和互斥信号量的任务将被阻塞直到事件发生、该资源信号量和互斥信号量可用。一旦被解阻塞任务就执行恰当嘚事件处理程序。资源信号量和互斥信号量在任务同步中的应用对于将中断服务程序从冗长的事件处理中解放出来以缩短中断响应时间是佷有用的

互斥资源信号量和互斥信号量是一种特殊的二进制资源信号量和互斥信号量,用于解决具有内在的互斥问题:优先级继承、删除安全和对资源的递归访问等情况

对于一般的操作系统,一般互斥资源信号量和互斥信号量就是二值信号靓量但VxWoks中有非同寻常的意义。另外一个典型就是Linux内核也单独设立了互斥资源信号量和互斥信号量。

互斥资源信号量和互斥信号量与二进制不同点在于:

①定义一个互斥资源信号量和互斥信号量时其已经初始化完毕为可用,它仅用于互斥;

②仅能由取(semTake ())它的任务释放即由同一个任务申请然后使用完畢后释放;

优先级倒置发生在一个高优先级的任务被迫等待一段不确定时间,等待一个低优先级任务完成VxWorks允许使用优先级继承,在互斥資源信号量和互斥信号量中使用选项SEM-INVERSION-SAFE 将使能优先级继承算法,优先级继承协议确保拥有资源的任务以阻塞在该资源上的所有任务中优先級最高的任务的优先级执行直到它释放所拥有的所有资源信号量和互斥信号量,然后该任务返回到正常状态因此这个“继承的高优先級”任务受到不会被任何中间优先级任务抢占的保护。

另一个互斥问题涉及到任务删除在一个受资源信号量和互斥信号量保护的临界区,经常需要保护在临界区执行的任务不会被意外地删除删除一个在临界区执行的任务可能引起意想不到的后果,造成保护资源的资源信號量和互斥信号量不可用可能导致资源处于破坏状态,也就导致了其他要访问该资源的所有任务无法得到满足

()提供了防止任务被意外刪除的一种方法。同时互斥资源信号量和互斥信号量提供了选项SEM-DELETE-SAFE 使用这个选项,每次调用semTake ( )时隐含地使能了taskSafe()当每次调用semGive ()时隐含地使能了taskUnsafe ()這种方式,任务得到资源信号量和互斥信号量时得到不会被删除的保护

互斥资源信号量和互斥信号量能够被递归地获得。这意味着资源信号量和互斥信号量能够被一个拥有该资源信号量和互斥信号量的任务在该资源信号量和互斥信号量最终被释放之前多次获取递归对于滿足一些子程序即要求能够相互调用但是也要求互斥访问一个资源非常有用。这种情形是可能的因为系统需要跟踪哪一个任务当前拥有資源信号量和互斥信号量。

计数器资源信号量和互斥信号量是实现任务同步和互斥的另一种手段在具体实现上有点差异。计数器资源信號量和互斥信号量除了像二进制资源信号量和互斥信号量那样工作外还保持对资源信号量和互斥信号量释放次数的跟踪。与二进制资源信号量和互斥信号量不同的时计数型资源信号量和互斥信号量每次释放,计数器加一;每次获取计数器减一,当资源信号量和互斥信號量减到时试图获取该资源信号量和互斥信号量的任务被阻塞。

正如二进制资源信号量和互斥信号量当计数资源信号量和互斥信号量釋放时,如果有任务阻塞在该资源信号量和互斥信号量阻塞队列上那么任务解除阻塞;但是如果资源信号量和互斥信号量释放时,没有任务阻塞在该资源信号量和互斥信号量阻塞队列上那么计数器加一。

通过对嵌入式操作系统VxWorks的多任务之间的通信机制的分析可以看出資源信号量和互斥信号量在实现多任务间的通信、同步和互斥中发挥着重要的作用。因此深入理解和正确使用VxWorks的资源信号量和互斥信号量,可以提高实时系统中多任务间通信的效率

}

我要回帖

更多关于 资源信号量和互斥信号量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信