のらぬこの日常を描く

ノージャンルのお役立ち情報やアニメとゲームの話、ソフトウェア開発に関する話などを中心としたブログです。

【Win7】svchostのメモリー大量消費を無理やりなんとかした話

プロローグ

我が家にはWindows7が稼働している家鯖もどきがある。

主にメディアサーバ、svnサーバ、テレビとつないでWMCによる動画音楽鑑賞、メディアプレーヤーのリモートプレイサーバ等として使っている。

外からいろいろいじれるようにsshdが入っていたり、一応httpdも入っているけどあんまり使ってない。

sshdは、会社からrdpしたいときのトンネル役としてそれなりに活躍する場面もあるが。

事件は自宅で起きた

しかしこの鯖、一つ困ったことがある。

MS製サービスの一部がたまに(割と頻繁にという表現のほうが適切かもしれない)メモリリークするのだ。

f:id:mikenekoDX:20120513110151p:image

こんな感じに、svchost.exe が 2.5Gbのメモリーを食ってるのがお分かりかと。

ちなみに、使用メモリーは秒速100kbくらいの速度で増加して、物理メモリーを食いつぶすと 大抵 OSを巻き込んで[DEAD END]となる。

ちなみに、問題のsvchost.exe が管理している service は↓のような感じ。

f:id:mikenekoDX:20120513110149p:image

いろいろ試してみると、[Homegroup Listener] というサービスを再起動すると、これ以上のメモリーリークを抑えることができる場合もある。

しかし、これまでにメモリリークした分は返してくれないぽいし、サービス再起動後、メモリーリークが収まるかというと微妙だったりする。

[homegroup listener]を止めてしまう手もあるのだが、自宅の環境は、homegroup にガリガリ依存してるので、それは×。

解決編

試しに、svchost.exe をタスクマネージャから殺したところ、メモリ使用量は当然のごとくガクッと減った。

しかも、殺したはずのサービスは、勝手に再起動されるし、さらに、メモリーリークも収まっている。ぽい。

f:id:mikenekoDX:20120513110150p:image

なので、メモリーを食いつぶしてそうなsvchost.exeプロセスがあったら、対象プロセスを殺して差し上げればよろしいのではないかという発想。

linux だと、top|grep した結果を awk して、pid とメモリー使用量を引っ張ってきて、対象プロセスのメモリ使用量が 一定以上だったら pkill してあげるという .sh を書くことになると思われますが

これをwindowsでやると下のような感じになります(vb2008くらいでコンパイルできます)

'KillSvcHost.vb
Module killSvchost

    Sub Main()
        KillProcess(GetCommandLines("C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted", 256 * 1024 * 1024))
    End Sub

    Public Function GetCommandLines(ByVal targetCommandline As String, ByVal limitWorkingSetSize As Long) As Long
        Dim result As Long = 0
        Using m As New Management.ManagementClass("Win32_Process"), ps = m.GetInstances()
            For Each p In ps
                Dim pid As Long = p("Handle")
                Dim commandline As String = CStr(p("CommandLine"))
                If commandline IsNot Nothing Then
                    Dim workingSetSize As Long = p("WorkingSetSize")
                    If (String.Compare(commandline, targetCommandline, True) = 0) And (workingSetSize > limitWorkingSetSize) Then
                        result = pid
                        Console.WriteLine(commandline & ":" & workingSetSize)
                    End If
                    p.Dispose()
                End If
            Next
        End Using
        Return result
    End Function

    Public Function KillProcess(ByVal pid As Long) As Boolean
        Dim p As System.Diagnostics.Process
        Dim result As Boolean = False
        p = System.Diagnostics.Process.GetProcessById(pid)
        If p IsNot Nothing Then
            Try
                Console.WriteLine("Kill:" & CStr(pid))
                p.Kill()
                Console.WriteLine("successful..")
                result = True
            Catch ex As Exception
                Console.WriteLine("failed...")
            End Try
        End If

        Return result
    End Function

End Module

コマンドライン

C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted

で起動されたプロセスを探し、ワーキングセットサイズ(共有メモリー部分も含めた、プロセスの使用メモリー)が 256Mb 超えていたらとりあえず殺します。

これをタスク(linuxでいうcrond)に登録して、適当な間隔で動かせばよい。

モリーリークが起きだしてしばらくすると、問題を起こした svchost.exe は勝手に殺されるというわけ。

ちなみに、タスクマネージャーでは、プライベートワーキングセットサイズとして表示されるのですが、これは、プロセスが使用しているメモリーのうち、非共有部分(そのプロセスしか絶対に使っていない部分のメモリー)のサイズを表示している。

ただし、プライベートワーキングセットサイズの計算は微妙にめんどくさいのでそこまではやりません。

プロセスが握っている全ページを取得し、各ページが共有メモリーなのか非共有なのかを調べて、非共有メモリーのページサイズのみを合算する。だけなんだけど。

とりあえず、解決できたのでよしとする。

 

 

インサイドWindows 第7版 上 システムアーキテクチャ、プロセス、スレッド、メモリ管理、他 (マイクロソフト公式解説書)

インサイドWindows 第7版 上 システムアーキテクチャ、プロセス、スレッド、メモリ管理、他 (マイクロソフト公式解説書)