gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/pgalloc/pgalloc_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package pgalloc
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  
    21  	"gvisor.dev/gvisor/pkg/hostarch"
    22  )
    23  
    24  const (
    25  	page     = hostarch.PageSize
    26  	hugepage = hostarch.HugePageSize
    27  	topPage  = (1 << 63) - page
    28  )
    29  
    30  func TestFindUnallocatedRange(t *testing.T) {
    31  	for _, test := range []struct {
    32  		name       string
    33  		usage      []usageFlatSegment
    34  		fileSize   int64
    35  		length     uint64
    36  		alignment  uint64
    37  		direction  Direction
    38  		want       uint64
    39  		expectFail bool
    40  	}{
    41  		{
    42  			name:      "Initial allocation succeeds",
    43  			length:    page,
    44  			alignment: page,
    45  			direction: BottomUp,
    46  			want:      0,
    47  		},
    48  		{
    49  			name:      "Initial allocation succeeds",
    50  			length:    page,
    51  			alignment: page,
    52  			direction: TopDown,
    53  			want:      chunkSize - page, // Grows by chunkSize, allocate down.
    54  		},
    55  		{
    56  			name: "Allocation begins at start of file",
    57  			usage: []usageFlatSegment{
    58  				{page, 2 * page, usageInfo{refs: 1}},
    59  			},
    60  			length:    page,
    61  			alignment: page,
    62  			direction: BottomUp,
    63  			want:      0,
    64  		},
    65  		{
    66  			name: "Allocation finds empty space at start of file",
    67  			usage: []usageFlatSegment{
    68  				{page, 2 * page, usageInfo{refs: 1}},
    69  			},
    70  			fileSize:  2 * page,
    71  			length:    page,
    72  			alignment: page,
    73  			direction: TopDown,
    74  		},
    75  		{
    76  			name: "Allocation finds empty space at end of file",
    77  			usage: []usageFlatSegment{
    78  				{0, page, usageInfo{refs: 1}},
    79  			},
    80  			fileSize:  2 * page,
    81  			length:    page,
    82  			alignment: page,
    83  			direction: TopDown,
    84  			want:      page,
    85  		},
    86  		{
    87  			name: "In-use frames are not allocatable",
    88  			usage: []usageFlatSegment{
    89  				{0, page, usageInfo{refs: 1}},
    90  				{page, 2 * page, usageInfo{refs: 2}},
    91  			},
    92  			length:    page,
    93  			alignment: page,
    94  			direction: BottomUp,
    95  			want:      2 * page,
    96  		},
    97  		{
    98  			name: "In-use frames are not allocatable",
    99  			usage: []usageFlatSegment{
   100  				{0, page, usageInfo{refs: 1}},
   101  				{page, 2 * page, usageInfo{refs: 2}},
   102  			},
   103  			fileSize:  2 * page,
   104  			length:    page,
   105  			alignment: page,
   106  			direction: TopDown,
   107  			want:      3 * page, // Double fileSize, allocate top-down.
   108  		},
   109  		{
   110  			name: "Reclaimable frames are not allocatable",
   111  			usage: []usageFlatSegment{
   112  				{0, page, usageInfo{refs: 1}},
   113  				{page, 2 * page, usageInfo{refs: 0}},
   114  				{2 * page, 3 * page, usageInfo{refs: 1}},
   115  			},
   116  			length:    page,
   117  			alignment: page,
   118  			direction: BottomUp,
   119  			want:      3 * page,
   120  		},
   121  		{
   122  			name: "Reclaimable frames are not allocatable",
   123  			usage: []usageFlatSegment{
   124  				{0, page, usageInfo{refs: 1}},
   125  				{page, 2 * page, usageInfo{refs: 0}},
   126  				{2 * page, 3 * page, usageInfo{refs: 1}},
   127  			},
   128  			fileSize:  3 * page,
   129  			length:    page,
   130  			alignment: page,
   131  			direction: TopDown,
   132  			want:      5 * page, // Double fileSize, grow down.
   133  		},
   134  		{
   135  			name: "Gaps between in-use frames are allocatable",
   136  			usage: []usageFlatSegment{
   137  				{0, page, usageInfo{refs: 1}},
   138  				{2 * page, 3 * page, usageInfo{refs: 1}},
   139  			},
   140  			length:    page,
   141  			alignment: page,
   142  			direction: BottomUp,
   143  			want:      page,
   144  		},
   145  		{
   146  			name: "Gaps between in-use frames are allocatable",
   147  			usage: []usageFlatSegment{
   148  				{0, page, usageInfo{refs: 1}},
   149  				{2 * page, 3 * page, usageInfo{refs: 1}},
   150  			},
   151  			fileSize:  3 * page,
   152  			length:    page,
   153  			alignment: page,
   154  			direction: TopDown,
   155  			want:      page,
   156  		},
   157  		{
   158  			name: "Inadequately-sized gaps are rejected",
   159  			usage: []usageFlatSegment{
   160  				{0, page, usageInfo{refs: 1}},
   161  				{2 * page, 3 * page, usageInfo{refs: 1}},
   162  			},
   163  			length:    2 * page,
   164  			alignment: page,
   165  			direction: BottomUp,
   166  			want:      3 * page,
   167  		},
   168  		{
   169  			name: "Inadequately-sized gaps are rejected",
   170  			usage: []usageFlatSegment{
   171  				{0, page, usageInfo{refs: 1}},
   172  				{2 * page, 3 * page, usageInfo{refs: 1}},
   173  			},
   174  			fileSize:  3 * page,
   175  			length:    2 * page,
   176  			alignment: page,
   177  			direction: TopDown,
   178  			want:      4 * page, // Double fileSize, grow down.
   179  		},
   180  		{
   181  			name: "Alignment is honored at end of file",
   182  			usage: []usageFlatSegment{
   183  				{0, page, usageInfo{refs: 1}},
   184  				// Hugepage-sized gap here that shouldn't be allocated from
   185  				// since it's incorrectly aligned.
   186  				{hugepage + page, hugepage + 2*page, usageInfo{refs: 1}},
   187  			},
   188  			length:    hugepage,
   189  			alignment: hugepage,
   190  			direction: BottomUp,
   191  			want:      2 * hugepage,
   192  		},
   193  		{
   194  			name: "Alignment is honored at end of file",
   195  			usage: []usageFlatSegment{
   196  				{0, page, usageInfo{refs: 1}},
   197  				// Hugepage-sized gap here that shouldn't be allocated from
   198  				// since it's incorrectly aligned.
   199  				{hugepage + page, hugepage + 2*page, usageInfo{refs: 1}},
   200  			},
   201  			fileSize:  hugepage + 2*page,
   202  			length:    hugepage,
   203  			alignment: hugepage,
   204  			direction: TopDown,
   205  			want:      3 * hugepage, // Double fileSize until alignment is satisfied, grow down.
   206  		},
   207  		{
   208  			name: "Alignment is honored before end of file",
   209  			usage: []usageFlatSegment{
   210  				{0, page, usageInfo{refs: 1}},
   211  				// Page will need to be shifted down from top.
   212  				{2*hugepage + page, 2*hugepage + 2*page, usageInfo{refs: 1}},
   213  			},
   214  			fileSize:  2*hugepage + 2*page,
   215  			length:    hugepage,
   216  			alignment: hugepage,
   217  			direction: TopDown,
   218  			want:      hugepage,
   219  		},
   220  		{
   221  			name:      "Allocation doubles file size more than once if necessary",
   222  			fileSize:  page,
   223  			length:    4 * page,
   224  			alignment: page,
   225  			direction: BottomUp,
   226  			want:      0,
   227  		},
   228  		{
   229  			name:      "Allocation doubles file size more than once if necessary",
   230  			fileSize:  page,
   231  			length:    4 * page,
   232  			alignment: page,
   233  			direction: TopDown,
   234  			want:      0,
   235  		},
   236  		{
   237  			name: "Allocations are compact if possible",
   238  			usage: []usageFlatSegment{
   239  				{page, 2 * page, usageInfo{refs: 1}},
   240  				{3 * page, 4 * page, usageInfo{refs: 2}},
   241  			},
   242  			fileSize:  4 * page,
   243  			length:    page,
   244  			alignment: page,
   245  			direction: TopDown,
   246  			want:      2 * page,
   247  		},
   248  		{
   249  			name: "Top-down allocation within one gap",
   250  			usage: []usageFlatSegment{
   251  				{page, 2 * page, usageInfo{refs: 1}},
   252  				{4 * page, 5 * page, usageInfo{refs: 2}},
   253  				{7 * page, 8 * page, usageInfo{refs: 1}},
   254  			},
   255  			fileSize:  8 * page,
   256  			length:    page,
   257  			alignment: page,
   258  			direction: TopDown,
   259  			want:      6 * page,
   260  		},
   261  		{
   262  			name: "Top-down allocation between multiple gaps",
   263  			usage: []usageFlatSegment{
   264  				{page, 2 * page, usageInfo{refs: 1}},
   265  				{3 * page, 4 * page, usageInfo{refs: 2}},
   266  				{5 * page, 6 * page, usageInfo{refs: 1}},
   267  			},
   268  			fileSize:  6 * page,
   269  			length:    page,
   270  			alignment: page,
   271  			direction: TopDown,
   272  			want:      4 * page,
   273  		},
   274  		{
   275  			name: "Top-down allocation with large top gap",
   276  			usage: []usageFlatSegment{
   277  				{page, 2 * page, usageInfo{refs: 1}},
   278  				{3 * page, 4 * page, usageInfo{refs: 2}},
   279  			},
   280  			fileSize:  8 * page,
   281  			length:    page,
   282  			alignment: page,
   283  			direction: TopDown,
   284  			want:      7 * page,
   285  		},
   286  		{
   287  			name: "Gaps found with possible overflow",
   288  			usage: []usageFlatSegment{
   289  				{page, 2 * page, usageInfo{refs: 1}},
   290  				{topPage - page, topPage, usageInfo{refs: 1}},
   291  			},
   292  			fileSize:  topPage,
   293  			length:    page,
   294  			alignment: page,
   295  			direction: TopDown,
   296  			want:      topPage - 2*page,
   297  		},
   298  		{
   299  			name: "Overflow detected",
   300  			usage: []usageFlatSegment{
   301  				{page, topPage, usageInfo{refs: 1}},
   302  			},
   303  			fileSize:   topPage,
   304  			length:     2 * page,
   305  			alignment:  page,
   306  			direction:  BottomUp,
   307  			expectFail: true,
   308  		},
   309  		{
   310  			name: "Overflow detected",
   311  			usage: []usageFlatSegment{
   312  				{page, topPage, usageInfo{refs: 1}},
   313  			},
   314  			fileSize:   topPage,
   315  			length:     2 * page,
   316  			alignment:  page,
   317  			direction:  TopDown,
   318  			expectFail: true,
   319  		},
   320  		{
   321  			name: "start may be in the middle of segment",
   322  			usage: []usageFlatSegment{
   323  				{0, 2 * page, usageInfo{refs: 1}},
   324  				{3 * page, 4 * page, usageInfo{refs: 2}},
   325  			},
   326  			length:    page,
   327  			alignment: page,
   328  			direction: BottomUp,
   329  			want:      2 * page,
   330  		},
   331  	} {
   332  		name := fmt.Sprintf("%s (%v)", test.name, test.direction)
   333  		t.Run(name, func(t *testing.T) {
   334  			f := MemoryFile{fileSize: test.fileSize}
   335  			if err := f.usage.ImportSlice(test.usage); err != nil {
   336  				t.Fatalf("Failed to initialize usage from %v: %v", test.usage, err)
   337  			}
   338  			if fr, ok := f.findAvailableRange(test.length, test.alignment, test.direction); ok {
   339  				if test.expectFail {
   340  					t.Fatalf("findAvailableRange(%v, %x, %x, %x, %v): got: %x, want: fail", test.usage, test.fileSize, test.length, test.alignment, test.direction, fr.Start)
   341  				}
   342  				if fr.Start != test.want {
   343  					t.Errorf("findAvailableRange(%v, %x, %x, %x, %v): got: start=%x, want: %x", test.usage, test.fileSize, test.length, test.alignment, test.direction, fr.Start, test.want)
   344  				}
   345  				if fr.End != test.want+test.length {
   346  					t.Errorf("findAvailableRange(%v, %x, %x, %x, %v): got: end=%x, want: %x", test.usage, test.fileSize, test.length, test.alignment, test.direction, fr.End, test.want+test.length)
   347  				}
   348  			} else if !test.expectFail {
   349  				t.Fatalf("findAvailableRange(%v, %x, %x, %x, %v): failed, want: %x", test.usage, test.fileSize, test.length, test.alignment, test.direction, test.want)
   350  			}
   351  		})
   352  	}
   353  }