1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <err.h>
18#include <errno.h>
19#include <pthread.h>
20#include <stdint.h>
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <sys/mman.h>
25#include <unistd.h>
26
27#include <new>
28
29#include "Action.h"
30#include "Thread.h"
31#include "Threads.h"
32
33void* ThreadRunner(void* data) {
34  Thread* thread = reinterpret_cast<Thread*>(data);
35  while (true) {
36    thread->WaitForPending();
37    Action* action = thread->GetAction();
38    thread->AddTimeNsecs(action->Execute(thread->pointers()));
39    bool end_thread = action->EndThread();
40    thread->ClearPending();
41    if (end_thread) {
42      break;
43    }
44  }
45  return nullptr;
46}
47
48Threads::Threads(Pointers* pointers, size_t max_threads)
49    : pointers_(pointers), max_threads_(max_threads) {
50  size_t pagesize = getpagesize();
51  data_size_ = (max_threads_ * sizeof(Thread) + pagesize - 1) & ~(pagesize - 1);
52  max_threads_ = data_size_ / sizeof(Thread);
53
54  void* memory = mmap(nullptr, data_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
55  if (memory == MAP_FAILED) {
56    err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu\n",
57        data_size_, max_threads_);
58  }
59
60  if (Thread::ACTION_SIZE < Action::MaxActionSize()) {
61    err(1, "Thread action size is too small: ACTION_SIZE %zu, max size %zu\n",
62        Thread::ACTION_SIZE, Action::MaxActionSize());
63  }
64
65  threads_ = new (memory) Thread[max_threads_];
66}
67
68Threads::~Threads() {
69  if (threads_) {
70    munmap(threads_, data_size_);
71    threads_ = nullptr;
72    data_size_ = 0;
73  }
74}
75
76Thread* Threads::CreateThread(pid_t tid) {
77  if (num_threads_ == max_threads_) {
78    err(1, "Too many threads created, current max %zu.\n", num_threads_);
79  }
80  Thread* thread = FindEmptyEntry(tid);
81  if (thread == nullptr) {
82    err(1, "No empty entries found, current max %zu, num threads %zu\n",
83          max_threads_, num_threads_);
84  }
85  thread->tid_ = tid;
86  thread->pointers_ = pointers_;
87  thread->total_time_nsecs_ = 0;
88  if (pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread) == -1) {
89    err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
90  }
91
92  num_threads_++;
93  return thread;
94}
95
96Thread* Threads::FindThread(pid_t tid) {
97  size_t index = GetHashEntry(tid);
98  for (size_t entries = num_threads_; entries != 0; ) {
99    pid_t cur_tid = threads_[index].tid_;
100    if (cur_tid == tid) {
101      return threads_ + index;
102    }
103    if (cur_tid != 0) {
104      entries--;
105    }
106    if (++index == max_threads_) {
107      index = 0;
108    }
109  }
110  return nullptr;
111}
112
113void Threads::WaitForAllToQuiesce() {
114  for (size_t i = 0, threads = 0; threads < num_threads_; i++) {
115    pid_t cur_tid = threads_[i].tid_;
116    if (cur_tid != 0) {
117      threads++;
118      threads_[i].WaitForReady();
119    }
120  }
121}
122
123size_t Threads::GetHashEntry(pid_t tid) {
124  return tid % max_threads_;
125}
126
127Thread* Threads::FindEmptyEntry(pid_t tid) {
128  size_t index = GetHashEntry(tid);
129  for (size_t entries = 0; entries < max_threads_; entries++) {
130    if (threads_[index].tid_ == 0) {
131      return threads_ + index;
132    }
133    if (++index == max_threads_) {
134      index = 0;
135    }
136  }
137  return nullptr;
138}
139
140void Threads::Finish(Thread* thread) {
141  int ret = pthread_join(thread->thread_id_, nullptr);
142  if (ret != 0) {
143    fprintf(stderr, "pthread_join failed: %s\n", strerror(ret));
144    exit(1);
145  }
146  total_time_nsecs_ += thread->total_time_nsecs_;
147  thread->tid_ = 0;
148  num_threads_--;
149}
150
151void Threads::FinishAll() {
152  for (size_t i = 0; i < max_threads_; i++) {
153    if (threads_[i].tid_ != 0) {
154      threads_[i].CreateAction(0, "thread_done", nullptr);
155      threads_[i].SetPending();
156      Finish(threads_ + i);
157    }
158  }
159}
160