• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

kubernetes pod内容器状态OOMKilled和退出码137全流程

武飞扬头像
大叶子不小
帮助4

kubernetes pod内容器状态OOMKilled和退出码137全流程解析 - 简书

使用event_control监听memory cgroup的oom事件 - 简书

kubernetes/k8s CRI分析-kubelet删除pod分析 - 良凯尔 - 博客园

在kubernetes的实际生产实践中,经常会看到pod内的容器因为内存使用超限被内核kill掉,使用kubectl命令查看pod,可以看到容器的退出原因是OOMKilled,退出码是137。

文章导读

cgroup简介与使用
linux epoll原理分析
containerd代码解析
kubelet代码解析
使用event_control监听oom事件

经过前面几篇文章的铺垫与递进,本篇终于可以进入正题,已以下两条主线进行分析。

  1. 容器的退出原因OOMKilled是如何经由containerd更新到kubelet,并最终更新到Pod的status中。
  2. 退出码为何是137。

本文的分析思路是由下至上,先分析内核的oom是如何触发的,再分析进程被kill掉后,退出码是何时赋值的,最后再分析containerd-shim,containerd,kubelet是如何处理进程的oom状态和退出码。

场景再现

本次实验基于4.19内核的centos的系统,kubernetes版本为1.23.1,kubelet的运行时配置为containerd,containerd版本为1.4,cgroup使用v1版本。

首先,编辑一个Pod的yaml文件,配置Pod的restartPolicy为Nerver,内存限额设置为50M,容器镜像为mem_alloc:v1。该容器启动后会不断申请并使用内存,直到内存超限,被内核kill掉。

  1.  
    [root@localhost oom]# cat pod_oom.yaml
  2.  
    apiVersion: v1
  3.  
    kind: Pod
  4.  
    metadata:
  5.  
    name: lugl-oom-test
  6.  
    spec:
  7.  
    restartPolicy: Never
  8.  
    containers:
  9.  
    - name: oom-test
  10.  
    image: docker.io/registry/mem_alloc:v1
  11.  
    imagePullPolicy: IfNotPresent
  12.  
    resources:
  13.  
    limits:
  14.  
    memory: "50Mi"

使用kubectl create -f pod_oom.yaml创建该pod,过一会儿使用kubectl查看pod的状态(删除掉不相关的字段信息)。可以看到容器的退出原因(reason)为OOMKilled,退出码(exitCode)为137。其中还有个字段lastState可以记录上一次的容器的状态信息,这里为了简化分析,忽略该字段。

  1.  
    [root@localhost oom]# kubectl get pods lugl-oom-test -o yaml
  2.  
    apiVersion: v1
  3.  
    kind: Pod
  4.  
    spec:
  5.  
    containers:
  6.  
    - image: docker.io/registry/mem_alloc:v1
  7.  
    imagePullPolicy: IfNotPresent
  8.  
    name: oom-test
  9.  
    resources:
  10.  
    limits:
  11.  
    memory: 50Mi
  12.  
    requests:
  13.  
    memory: 50Mi
  14.  
    restartPolicy: Never
  15.  
     
  16.  
    status:
  17.  
    conditions:
  18.  
    - lastProbeTime: null
  19.  
    lastTransitionTime: "2022-02-20T02:10:30Z"
  20.  
    status: "True"
  21.  
    type: Initialized
  22.  
    containerStatuses:
  23.  
    - containerID: containerd://388f01cfb6b8ba2817ff85ef8e72c654f866200d96123a623a73e0304e4934cf
  24.  
    image: docker.io/registry/mem_alloc:v1
  25.  
    imageID: sha256:3bd194272e76fa429b1c5df19d91bcf47eacc1ed07e3afe378ec3d7b49524ef0
  26.  
    lastState: {}
  27.  
    name: oom-test
  28.  
    ready: false
  29.  
    restartCount: 0
  30.  
    started: false
  31.  
    state:
  32.  
    terminated:
  33.  
    containerID: containerd://388f01cfb6b8ba2817ff85ef8e72c654f866200d96123a623a73e0304e4934cf
  34.  
    exitCode: 137
  35.  
    finishedAt: "2022-02-20T02:10:42Z"
  36.  
    reason: OOMKilled
  37.  
    startedAt: "2022-02-20T02:10:31Z"
学新通

oom机制和信号处理

(1) 本环境中的PAGESIZE为4K,mem_alloc程序在运行中,会申请内存并使用,所以会不断触发缺页异常,内核在每次申请内存页时都会去比较该cgroup进程的内存使用量 是否超过了该cgroup内设置的阈值(memory.limit_in_bytes),如果超过阈值,并且在该cgroup内也没法通过内存回收释放足够的内存,则内核会在该cgroup内选择一个进程kill掉(oom_badness)。

(2)当触发oom时,内核会向进程发送SIGKILL信号,这里的发送信号是拟人化的写法,方便人去理解,但实际的内核发送信号仅仅是对进程数据结构(task_struct)中的信号相关字段的修改,如将SIGKILL这个sig添加到该进程的信号处理队列中,这样就算内核完成了信号的发送。

SIGKILL(对应信号值9)和SIGSTOP(对应信号值19)是两个特权信号,即用户不可以注册这两个信号的信号处理函数,由内核的默认函数进行处理。 SIGTERM(对应信号值15)是可以被注册信号处理函数的。kill命令不加参数默认就是向进程发送SIGTERM信号。kubelet停止pod时,也是先发送SIGTERM信号,经过terminationGracePeriodSeconds时间后,如果pod没有退出再发送SIGKILL信号。所以容器内的进程最好是注册SIGTERM对应的信号处理函数,在进程退出的时候做一些资源清理的操作,减少异常的发生,如关闭远端连接。容器内的init进程退出时,内核会向该init进程命名空间下的其他进程发送SIGKILL信号,更完善的做法是init进程拦截SIGTERM信号后,将SIGTERM转发给子进程,这样init和init的子进程都可以实现优雅退出,如docker的 --init参数就是使用一个专用的init进程接管用户的程序。

下面就是mem_alloc进程因为oom被kil掉的堆栈信息。

  1.  
    # 设置以下关注函数
  2.  
    [root@node-135 tracing]# cat set_ftrace_filter
  3.  
    __send_signal
  4.  
    do_send_sig_info
  5.  
    __oom_kill_process
  6.  
    out_of_memory
  7.  
    mem_cgroup_out_of_memory
  8.  
    try_charge
  9.  
    # cat trace_pipe
  10.  
    => __send_signal -- 向进程发送SIGKILL信号
  11.  
    => do_send_sig_info
  12.  
    => __oom_kill_process -- kill掉一些进程
  13.  
    => oom_kill_process
  14.  
    => out_of_memory -- 内存不足
  15.  
    => mem_cgroup_out_of_memory
  16.  
    => try_charge
  17.  
    => mem_cgroup_try_charge -- cgroup内的内存是否充足
  18.  
    => mem_cgroup_try_charge_delay
  19.  
    => __handle_mm_fault
  20.  
    => handle_mm_fault
  21.  
    => __do_page_fault
  22.  
    => do_page_fault
  23.  
    => page_fault -- 缺页异常
学新通

(3)内核修改了进程的task_struct,表明该进程有信号需要处理。用户注册的信号处理函数在用户态,此时还在内核中执行异常处理流程,那么信号处理函数的执行时机是什么呢? 在返回到用户态的时候是一个执行时机。信号处理的实现比较复杂,具体细节分析可以参见极客时间《趣谈linux操作系统》中的信号处理章节,详细介绍了信号处理函数执行的时机,以及执行完信号处理函数又是如何返回到正常的执行流程中的整个过程。

以下堆栈信息展示了mem_alloc程序的退出过程。

  1.  
    # 以下的函数堆栈是一个SIGKILL信号的处理
  2.  
    => __send_signal -- 向父进程发送SIGCHILD信号
  3.  
    => do_notify_parent
  4.  
    => do_exit -- 在这个函数里会设置进程的退出码
  5.  
    => do_group_exit -- 这个函数的参数只有一个退出码
  6.  
    => get_signal -- 获取需要处理的信号
  7.  
    => do_signal
  8.  
    => exit_to_usermode_loop -- 返回到用户态
  9.  
    => prepare_exit_to_usermode
  10.  
    => swapgs_restore_regs_and_return_to_usermode

(4)正常的程序在退出时是调用exit系统调用,exit是调用do_exit函数,在(3)的堆栈中可以看到处理SIGKILL也会调用do_exit,区别在于正常的退出,错误码是经过了处理的,而在处理SIGKILL信号时,是直接将信号的值赋值给了进程的退出码(task_struct.exit_code)。

  1.  
    SYSCALL_DEFINE1(exit, int, error_code)
  2.  
    {
  3.  
    do_exit((error_code&0xff)<<8);
  4.  
    }

(5)使用$?查看程序的退出码,这也上面的分析不一致,错误码应该是9,还不是137,问题在哪里呢?

  1.  
    [root@localhost test]# echo 104857600 > memory.limit_in_bytes
  2.  
    [root@localhost test]# echo $$ > cgroup.procs
  3.  
    [root@localhost test]# /root/training/memory/oom/mem-alloc/mem_alloc 40000
  4.  
    Allocating,set to 40000 Mbytes
  5.  
    Killed
  6.  
    [root@localhost test]# echo $?
  7.  
    137

(6)重复(5)中的步骤,使用ftrace查看函数do_exit的参数值。从下面的trace_pipe中确实看到mem_alloc的退出码确实是9,而不是137。这里直接说结论,在下面会再次验证,bash进程是mem_alloc的父进程,在处理mem_alloc进程的返回值时,如果是9,说明是被SIGKILL掉的,所以将9加上一个偏移量128,结果就是137,这样做可能是为了与linux系统标准的错误码做一个区分吧。

  1.  
    # 设置kprobe
  2.  
    [root@localhost tracing]# echo 'p:myprobe do_exit exit_code=%di' > kprobe_events
  3.  
    [root@localhost tracing]# echo 1 > events/kprobes/myprobe/enable
  4.  
    # 设置过滤条件
  5.  
    [root@localhost tracing]# echo exit_code==9 > events/kprobes/myprobe/filter
  6.  
    [root@localhost tracing]# /root/training/memory/oom/mem-alloc/mem_alloc 40000
  7.  
    Allocating,set to 40000 Mbytes
  8.  
    Killed
  9.  
    #查看结果
  10.  
    [root@localhost tracing]# cat trace_pipe
  11.  
    mem_alloc-17077 [001] .... 875.147095: myprobe: (do_exit 0x0/0xc60) exit_code=0x9

containerd-shim 进程的处理

containerd-shim监听cgroup内进程的oom事件与使用event_control监听oom事件中的一致,这里不再赘述。

(1) 在Run函数中会监听eventfd,如果epoll_wait返回,则说明该cgroup内有进程触发了oom。

  1.  
    func (e *epoller) Run(ctx context.Context) {
  2.  
    var events [128]unix.EpollEvent
  3.  
    for {
  4.  
    select {
  5.  
    default:
  6.  
    n, err := unix.EpollWait(e.fd, events[:], -1)
  7.  
    for i := 0; i < n; i {
  8.  
    e.process(ctx, uintptr(events[i].Fd))
  9.  
    }
  10.  
    }
  11.  
    }
  12.  
    }

(2) process函数中会通过GRPC消息将TaskOOMEvent转发到containerd中。

  1.  
    func (e *epoller) process(ctx context.Context, fd uintptr) {
  2.  
    if err := e.publisher.Publish(ctx, runtime.TaskOOMEventTopic, &eventstypes.TaskOOM{
  3.  
    ContainerID: i.id,
  4.  
    });
  5.  
    }

(3)在进程退出时,内核会向其父进程(containerd-shim)发送SIGCHILD信号。handleSignals会处理SIGCHILD信号,调用Reap,进而使用系统调用wait4拿到进程的退出码。

  1.  
    // runtime/v2/shim/shim_unix.go
  2.  
    func handleSignals(ctx context.Context, logger *logrus.Entry, signals chan os.Signal) error {
  3.  
    for {
  4.  
    select {
  5.  
    case s := <-signals:
  6.  
    switch s {
  7.  
    case unix.SIGCHLD:
  8.  
    if err := reaper.Reap(); err != nil {
  9.  
    logger.WithError(err).Error("reap exit status")
  10.  
    }
  11.  
    }
  12.  
    }
  13.  
    }

(4)在containerd-shim启动的时候,会启动goroutine监听容器进程的退出,checkProcess中会将容器id,进程id,退出码,退出时间等信息通过GRPC消息转发到containerd中。

  1.  
    func New(ctx context.Context, id string, publisher shim.Publisher, shutdown func()) (shim.Shim, error) {
  2.  
    if cgroups.Mode() == cgroups.Unified {
  3.  
    ep, err = oomv2.New(publisher)
  4.  
    } else {
  5.  
    ep, err = oomv1.New(publisher)
  6.  
    }
  7.  
     
  8.  
    go ep.Run(ctx)
  9.  
     
  10.  
    go s.processExits()
  11.  
     
  12.  
    go s.forward(ctx, publisher)
  13.  
    }
  14.  
     
  15.  
    func (s *service) checkProcesses(e runcC.Exit) {
  16.  
    for _, container := range s.containers {
  17.  
    for _, p := range container.All() {
  18.  
    p.SetExited(e.Status)
  19.  
    s.sendL(&eventstypes.TaskExit{
  20.  
    ContainerID: container.ID,
  21.  
    ID: p.ID(),
  22.  
    Pid: uint32(e.Pid),
  23.  
    ExitStatus: uint32(e.Status),
  24.  
    ExitedAt: p.ExitedAt(),
  25.  
    })
  26.  
    }
  27.  
    }
  28.  
    }
学新通

(5)回收子进程会调用reap函数,在exitStatus中,会判断进程是否是被信号中断的,如果是的话,则加上一个偏移量128,这里就验证了上面说的结论,137 = 9 128。那么可以推断bash中 $?也是同样的处理。

  1.  
    func reap(wait bool) (exits []exit, err error) {
  2.  
    for {
  3.  
    pid, err := unix.Wait4(-1, &ws, flag, &rus)
  4.  
    exits = append(exits, exit{
  5.  
    Pid: pid,
  6.  
    Status: exitStatus(ws),
  7.  
    })
  8.  
    }
  9.  
    }
  10.  
     
  11.  
    // sys/reaper/reaper_unix.go
  12.  
    const exitSignalOffset = 128
  13.  
    func exitStatus(status unix.WaitStatus) int {
  14.  
    if status.Signaled() {
  15.  
    return exitSignalOffset int(status.Signal())
  16.  
    }
  17.  
    return status.ExitStatus()
  18.  
    }
  19.  
     
  20.  
    // unix/syscall_linux.go
  21.  
    const (
  22.  
    mask = 0x7F
  23.  
    core = 0x80
  24.  
    exited = 0x00
  25.  
    stopped = 0x7F
  26.  
    shift = 8
  27.  
    )
  28.  
    func (w WaitStatus) Signaled() bool {
  29.  
    return w&mask != stopped && w&mask != exited
  30.  
    }
学新通

containerd的处理

(1)在handleEvent中会处理containerd-shim转发过来的容器的事件信息,如果是TaskOOM,则调用UpdateSync更新容器的退出原因。
(2)如果是进程退出,则调用handleContainerExit,再调用UpdateSync更新容器的退出码。

  1.  
    // handleEvent handles a containerd event.
  2.  
    func (em *eventMonitor) handleEvent(any interface{}) error {
  3.  
    switch e := any.(type) {
  4.  
    case *eventtypes.TaskExit:
  5.  
    logrus.Infof("TaskExit event % v", e)
  6.  
    cntr, err := em.c.containerStore.Get(e.ID)
  7.  
    handleContainerExit(ctx, e, cntr);
  8.  
    case *eventtypes.TaskOOM:
  9.  
    logrus.Infof("TaskOOM event % v", e)
  10.  
    // For TaskOOM, we only care which container it belongs to.
  11.  
    cntr, err := em.c.containerStore.Get(e.ContainerID)
  12.  
    err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
  13.  
    status.Reason = oomExitReason
  14.  
    return status, nil
  15.  
    })
  16.  
    return nil
  17.  
    }
  18.  
     
  19.  
    func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr containerstore.Container) error {
  20.  
    err = cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) {
  21.  
    if status.FinishedAt == 0 {
  22.  
    status.Pid = 0
  23.  
    status.FinishedAt = e.ExitedAt.UnixNano()
  24.  
    status.ExitCode = int32(e.ExitStatus)
  25.  
    }
  26.  
     
  27.  
    return status, nil
  28.  
    })
  29.  
    }
学新通

(3)UpdateSync是containerd中用来更新容器存储状态的函数。

  1.  
    func (s *statusStorage) UpdateSync(u UpdateFunc) error {
  2.  
    newStatus, err := u(s.status)
  3.  
    data, err := newStatus.encode()
  4.  
    continuity.AtomicWriteFile(s.path, data, 0600)
  5.  
    s.status = newStatus
  6.  
    }

(4)containerd的处理就算完成了,接下来就等kubelet来获取容器的状态了。

kubelet的处理

kubelet的细节分析参见 kubelet代码解析
(1)relist函数会定期执行,对比pod状态的变化。
(2)通过computeEvent、updateEvents和generateEvents对比内存中的pod的状态和从runtime接口获取的实时的pod的状态差异,从而可以推断出pod内的容器发生了状态变化。

  1.  
    func (g *GenericPLEG) Start() {
  2.  
    go wait.Until(g.relist, g.relistPeriod, wait.NeverStop)
  3.  
    }
  4.  
    func (g *GenericPLEG) relist() {
  5.  
    klog.V(5).InfoS("GenericPLEG: Relisting")
  6.  
    for pid := range g.podRecords {
  7.  
    oldPod := g.podRecords.getOld(pid)
  8.  
    pod := g.podRecords.getCurrent(pid)
  9.  
    // Get all containers in the old and the new pod.
  10.  
    allContainers := getContainersFromPods(oldPod, pod)
  11.  
    for _, container := range allContainers {
  12.  
    events := computeEvents(oldPod, pod, &container.ID)
  13.  
    for _, e := range events {
  14.  
    updateEvents(eventsByPodID, e)
  15.  
    }
  16.  
    }
  17.  
    }
  18.  
    }
学新通

(3)如因oom产生一个类型为ContainerDied的PodLifecycleEvent事件

  1.  
    func generateEvents(podID types.UID, cid string, oldState, newState plegContainerState) []*PodLifecycleEvent {
  2.  
    klog.V(4).InfoS("GenericPLEG", "podUID", podID, "containerID", cid, "oldState", oldState, "newState", newState)
  3.  
    switch newState {
  4.  
    case plegContainerRunning:
  5.  
    return []*PodLifecycleEvent{{ID: podID, Type: ContainerStarted, Data: cid}}
  6.  
    case plegContainerExited:
  7.  
    return []*PodLifecycleEvent{{ID: podID, Type: ContainerDied, Data: cid}}
  8.  
    case plegContainerUnknown:
  9.  
    return []*PodLifecycleEvent{{ID: podID, Type: ContainerChanged, Data: cid}}
  10.  
    }
  11.  
     

(4)在syncLoopIteration中会处理该pleg事件,继而调用Podworker的sync方法。

  1.  
    func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
  2.  
    syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
  3.  
    select {
  4.  
    case e := <-plegCh:
  5.  
    handler.HandlePodSyncs([]*v1.Pod{pod})
  6.  
    }

(5)generateAPIPodStatus会将容器的状态转换为 v1.PodStatus定义中的字段。再调用statusManager的SetPodStatus方法更新pod的状态信息。

  1.  
    func (kl *Kubelet) syncPod(ctx context.Context, updateType kubetypes.SyncPodType, pod, mirrorPod *v1.Pod, podStatus *kubecontainer.PodStatus) error {
  2.  
    klog.V(4).InfoS("syncPod enter", "pod", klog.KObj(pod), "podUID", pod.UID)
  3.  
     
  4.  
    // Generate final API pod status with pod and status manager status
  5.  
    apiPodStatus := kl.generateAPIPodStatus(pod, podStatus)
  6.  
     
  7.  
    kl.statusManager.SetPodStatus(pod, apiPodStatus)
  8.  
     
  9.  
    // Call the container runtime's SyncPod callback
  10.  
    result := kl.containerRuntime.SyncPod(pod, podStatus, pullSecrets, kl.backOff)
  11.  
    }
  12.  
     

(6)根据状态做不同的赋值,如ContainerStateExited,则要更新容器的退出码,原因等。

  1.  
    func (kl *Kubelet) convertToAPIContainerStatuses(pod *v1.Pod, podStatus *kubecontainer.PodStatus, previousStatus []v1.ContainerStatus, containers []v1.Container, hasInitContainers, isInitContainer bool) []v1.ContainerStatus {
  2.  
    switch {
  3.  
    case cs.State == kubecontainer.ContainerStateRunning:
  4.  
    status.State.Running = &v1.ContainerStateRunning{StartedAt: metav1.NewTime(cs.StartedAt)}
  5.  
    case cs.State == kubecontainer.ContainerStateCreated:
  6.  
    fallthrough
  7.  
    case cs.State == kubecontainer.ContainerStateExited:
  8.  
    status.State.Terminated = &v1.ContainerStateTerminated{
  9.  
    ExitCode: int32(cs.ExitCode),
  10.  
    Reason: cs.Reason,
  11.  
    Message: cs.Message,
  12.  
    StartedAt: metav1.NewTime(cs.StartedAt),
  13.  
    FinishedAt: metav1.NewTime(cs.FinishedAt),
  14.  
    ContainerID: cid,
  15.  
    }
  16.  
    }
学新通

(7)getPhase也是一个比较重要的函数,这里会决定pod的状态,PodSpec中的restartPolicy是在这里生效的。

  1.  
    func getPhase(spec *v1.PodSpec, info []v1.ContainerStatus) v1.PodPhase {
  2.  
    for _, container := range spec.Containers {
  3.  
    containerStatus, ok := podutil.GetContainerStatus(info, container.Name)
  4.  
    if !ok {
  5.  
    unknown
  6.  
    continue
  7.  
    }
  8.  
     
  9.  
    switch {
  10.  
    case containerStatus.State.Running != nil:
  11.  
    running
  12.  
    case containerStatus.State.Terminated != nil:
  13.  
    stopped
  14.  
    if containerStatus.State.Terminated.ExitCode == 0 {
  15.  
    succeeded
  16.  
    }
  17.  
    }
  18.  
     
  19.  
    switch {
  20.  
    case running > 0 && unknown == 0:
  21.  
    return v1.PodRunning
  22.  
    case running == 0 && stopped > 0 && unknown == 0:
  23.  
    if spec.RestartPolicy == v1.RestartPolicyAlways {
  24.  
    // All containers are in the process of restarting
  25.  
    return v1.PodRunning
  26.  
    }
  27.  
    if stopped == succeeded {
  28.  
    return v1.PodSucceeded
  29.  
    }
  30.  
    if spec.RestartPolicy == v1.RestartPolicyNever {
  31.  
    return v1.PodFailed
  32.  
    }
  33.  
    return v1.PodRunning
  34.  
    }
  35.  
    }
学新通

(8)status_manager中的syncPod方法会将新的pod状态通过patch方法更新到apiserver。

  1.  
    func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
  2.  
    pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(context.TODO(), status.podName, metav1.GetOptions{})
  3.  
    newPod, patchBytes, unchanged, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status))
  4.  
    klog.V(3).InfoS("Patch status for pod", "pod", klog.KObj(pod), "patch", string(patchBytes))
  5.  
    }

mem_alloc代码

参考极客时间 《容器高手实战课》

  1.  
    [root@localhost mem-alloc]# cat mem_alloc.c
  2.  
    #include <stdio.h>
  3.  
    #include <malloc.h>
  4.  
    #include <string.h>
  5.  
    #include <unistd.h>
  6.  
    #include <stdlib.h>
  7.  
     
  8.  
    #define BLOCK_SIZE (10*1024*1024)
  9.  
     
  10.  
    int main(int argc, char **argv)
  11.  
    {
  12.  
     
  13.  
    int thr, i;
  14.  
    char *p1;
  15.  
     
  16.  
    if (argc != 2) {
  17.  
    printf("Usage: mem_alloc <num (MB)>\n");
  18.  
    exit(0);
  19.  
    }
  20.  
     
  21.  
    thr = atoi(argv[1]);
  22.  
     
  23.  
    printf("Allocating," "set to %d Mbytes\n", thr);
  24.  
    sleep(10);
  25.  
    for (i = 0; i < thr; i ) {
  26.  
    p1 = malloc(BLOCK_SIZE);
  27.  
    memset(p1, 0x00, BLOCK_SIZE);
  28.  
    }
  29.  
     
  30.  
    sleep(600);
  31.  
     
  32.  
    return 0;
  33.  
    }
  34.  
     
  35.  
    [root@localhost oom]# cat Dockerfile
  36.  
    FROM nginx:latest
  37.  
     
  38.  
    COPY ./mem-alloc/mem_alloc /
  39.  
     
  40.  
    CMD ["/mem_alloc", "2000"]
  41.  
    [root@localhost oom]# cat Makefile
  42.  
    all: image
  43.  
     
  44.  
    mem_alloc: mem-alloc/mem_alloc.c
  45.  
    gcc -o mem-alloc/mem_alloc mem-alloc/mem_alloc.c
  46.  
    image: mem_alloc
  47.  
    docker build -t registry/mem_alloc:v1 .
  48.  
    clean:
  49.  
    rm mem-alloc/mem_alloc -f
  50.  
    docker stop mem_alloc;docker rm mem_alloc;docker rmi registry/mem_alloc:v1
  51.  
     
学新通

总结

本文研究的都是确定性的问题,好像没有什么意义,话说回来,如果没有学习这些确定性问题的积累,又如何去应对不确定的问题呢?

学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfgkfih
系列文章
更多 icon
同类精品
更多 icon
继续加载