Failing to execute "make" in Ubuntu gcc - c

I have several files which I'm trying to compile using gcc in Ubuntu. I have vmware machine 16.04 ubuntu, which 64-bit computer. I have added the flags for 32bit compilation in the makefile. (-m32 flag).
This is the makefile:
all: clean binsem.a ut.a ph
FLAGS = -Wall -L./ -m32
ph: ph.c
gcc ${FLAGS} ph.c -lbinsem -lut -o ph
binsem.a:
gcc $(FLAGS) -c binsem.c
ar rcu libbinsem.a binsem.o
ranlib libbinsem.a
ut.a:
gcc $(FLAGS) -c ut.c
ar rcu libut.a ut.o
ranlib libut.a
clean:
rm -f *.o
rm -f a.out
rm -f *~
rm -f ph
rm -f *a
My attempts to compile the code were not successful, as I received the following error:
ph.c:126:12: warning: ‘main’ defined but not used [-Wunused-function]
/usr/lib/gcc/i686-linux-gnu/5/../../../i386-linux-gnu/crt1.o: In function `_start':
(.text+0x18): undefined reference to `main'
collect2: error: ld returned 1 exit status
Makefile:6: recipe for target 'ph' failed
make: *** [ph] Error 1
**I tried - **
Adding -c flags to the ph.c file in the make file. However it doesn't make much sense, since it has a main function. No success.
Adding sum -m32 flags in the makefile, in addition to what's already written. No Success.
The file "ph.c", is the following file -
#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdint.h>
#include <sys/time.h>
#include <inttypes.h>
#include "binsem.h"
#include "ut.h"
#define LEFT (i+N-1)%N
#define RIGHT (i+1)%N
#define THINKING 0
#define HUNGRY 1
#define EATING 2
int N;
volatile int *phil_state;
sem_t *s;
sem_t mutex;
int *tid;
uint64_t get_wall_time() {
struct timeval time;
gettimeofday(&time, NULL);
uint64_t millis = (time.tv_sec * (uint64_t)1000) + (time.tv_usec / 1000);
return millis;
}
void think(int p) {
int i, factor;
volatile int j;
printf("Philosopher (%d) - time %" PRId64 " - is thinking\n",p, get_wall_time()); fflush (stdout);
factor = 1 + random()%5;
for (i = 0; i < 100000000*factor; i++){
j += (int) i*i;
}
printf("Philosopher (%d) - time %" PRId64 " - is hungry\n", p, get_wall_time()); fflush (stdout);
}
void eat(int p){
int i, factor;
volatile int j;
printf("Philosopher (%d) - time %" PRId64 " - is eating\n",p, get_wall_time()); fflush (stdout);
factor = 1 + random()%5;
for (i = 0; i < 100000000*factor; i++){
j += (int) i*i;
}
//printf("Philosopher (%d) - time %" PRId64 " - is thinking\n",p, get_wall_time()); fflush (stdout);
}
void test(int i){
if (phil_state[i] == HUNGRY &&
phil_state[LEFT] != EATING &&
phil_state[RIGHT] != EATING){
phil_state[i] = EATING;
binsem_up(&(s[i]));
}
}
void take_forks(int i){
binsem_down(&mutex);
phil_state[i] = HUNGRY;
test(i);
binsem_up(&mutex);
binsem_down(&(s[i]));
}
void put_forks(int i){
binsem_down(&mutex);
phil_state[i] = THINKING;
test(LEFT);
test(RIGHT);
binsem_up(&mutex);
}
void int_handler(int signo) {
long int duration;
int i;
for (i = 0; i < N; i++) {
duration = ut_get_vtime(tid[i]);
printf("Philosopher (%d) used the CPU %ld.%ld sec.\n",
i+1,duration/1000,duration%1000);
}
exit(0);
}
void philosopher(int i){
while (1){
think(i);
take_forks(i);
eat(i);
put_forks(i);
}
}
static int main(int argc, char *argv[])
{
int c;
if (argc != 2){
printf("Usage: %s N\n", argv[0]);
exit(1);
}
N = atoi(argv[1]);
if (N < 2){
printf("Usage: %s N (N >=2)\n", argv[0]);
exit(1);
}
ut_init(N);
s = (sem_t *)malloc (N * sizeof(sem_t));
phil_state = (int *) malloc (N * sizeof(int));
tid = (int *) malloc (N * sizeof(int));
for (c = 0; c < N ; c++){
phil_state[c] = THINKING;
binsem_init(&(s[c]), 0);
}
for (c = 0; c < N ; c++){
tid[c] = ut_spawn_thread(philosopher,c);
printf("Spawned thread #%d\n", tid[c]);
}
binsem_init(&mutex, 1);
signal(SIGINT,int_handler);
ut_start();
return 0; // avoid warnings
}
The makefile also tries to compile these files - ut.c, binsem.c
#include <stdlib.h>
#ifndef _UT_H
#define _UT_H
#include "ut.h"
#include <sys/time.h> // for itimerval
#include <unistd.h> // for alarm
#include <stdlib.h> // for malloc
#include <stdio.h> // for perror
#include <ucontext.h>
# include "ut.h"
#define MAX_TAB_SIZE 128 // the maximal threads table size.
#define MIN_TAB_SIZE 2 // the minimal threads table size.
#define SYS_ERR -1 // system-related failure code
#define TAB_FULL -2 // full threads table failure code
/*This type defines a single slot (entry) in the threads table. Each slot describes a single
thread. Note that we don't need to keep the thread state since every thread is always ready
or running. We also don't have to support adding/stopping thread dynamically, so we also don't
have to manage free slots.*/
typedef struct ut_slot {
ucontext_t uc;
unsigned long vtime; /* the CPU time (in milliseconds) consumed by this thread.*/
void (*func)(int); /* the function executed by the thread.*/
int arg; /* the function argument.*/
} ut_slot_t, *ut_slot;
static ut_slot threads; // pointer to thread table
static volatile int numThreads = 0; // number of threads in the table
static volatile int currentThread = 0; // current thread
static ucontext_t mainThread;
#define STACKSIZE 8192 // the thread stack size.
/* The TID (thread ID) type. TID of a thread is actually the index of the thread in the
threads table. */
typedef short int tid_t;
void handler(int signal){
alarm(1); //the alarm every second as demanded in the assignment
currentThread = (currentThread +1 ) % numThreads;
printf("in signal handler: switching from %d to %d\n", currentThread, currentThread - 1);
swapcontext(&threads[currentThread].uc, &threads[currentThread].uc); /*save current thread,
* load next thread*/
if (signal == SIGVTALRM){ // increment the time stats
threads[currentThread].vtime+=100;
} else if(signal==SIGALRM) {
if (swapcontext(&threads[currentThread - 1].uc, &threads[currentThread].uc) == -1) {
perror("could not do swapping");
exit(1);
}
}
}
int ut_init(int tab_size){
/// (###)
if(tab_size < MAX_TAB_SIZE) {
threads = (ut_slot) malloc(tab_size * sizeof(int(ut_slot_t)));
}
else{
threads = (ut_slot) malloc(MAX_TAB_SIZE * sizeof(int(ut_slot_t)));
}
if (!threads) {
return SYS_ERR;
}
return 0;
}
tid_t ut_spawn_thread(void (*func)(int), int arg){
/*uc[1].uc_link = &uc[0];
uc[1].uc_stack.ss_sp = st1; //stack fro thread 1
uc[1].uc_stack.ss_size = sizeof st1; //size of stack for therad
makecontext(&uc[1], (void(*)(void)) f, 1, 1); */
int thread_stack_size = STACKSIZE/8; //no need for 8K in size
if (numThreads>=TAB_FULL){ //(*)
return TAB_FULL;
}
if (getcontext(&threads[numThreads].uc)==-1){
return SYS_ERR;
}
ut_slot_t newThread;
threads[numThreads] = newThread;
threads[numThreads].uc.uc_link = &mainThread;
threads[numThreads].uc.uc_stack.ss_sp = (void* *)malloc(thread_stack_size);
if(threads[numThreads].uc.uc_stack.ss_sp==NULL){
return SYS_ERR;
}
makecontext(&threads[numThreads].uc,(void(*)(void))func,1,arg);
numThreads++;
return numThreads - 1;
}
int ut_start(void){
struct sigaction sigaction1;
int firstThread = 0; /*represents the current thread*/
struct itimerval itv;
/* set up vtimer for accounting */
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 10000;
itv.it_value = itv.it_interval;
/* Initialize the data structures for SIGALRM handling. */
sigaction1.sa_flags = SA_RESTART; //restart instead of throwing exception
sigfillset(&sigaction1.sa_mask); // don't throw exception for additional signals
sigaction1.sa_handler = handler; // specify handler for the sigaction declared
if (sigaction(SIGVTALRM, &sigaction1, NULL) < 0)
return SYS_ERR;
/*START THE TIMER */
if (setitimer(ITIMER_VIRTUAL, &itv, NULL) < 0)
return SYS_ERR;
if (sigaction(SIGINT, &sigaction1, NULL) < 0)
return SYS_ERR;
/* Start running. */
alarm(1); //alarm every second
if(swapcontext(&threads[firstThread].uc,&threads[firstThread+1].uc)==-1){ //swap first time
return SYS_ERR;
}
return -1;
}
unsigned long ut_get_vtime(tid_t tid){
return threads[tid].vtime;
}
#endif
binsem.c
#ifndef _BIN_SEM_H
#define _BIN_SEM_H
#include "binsem.h"
#include <signal.h>
#include "atomic.h"
typedef unsigned long sem_t;
void binsem_init(sem_t *s, int init_val){
if(init_val == 1){
*s = 1;
}
else{
*s = 0;
}
}
void binsem_up(sem_t *s){
xchg(s,1); /*send the pointer of s, and the new value 1*/
}
int binsem_down(sem_t *s){
int flag = 0;
while (flag == 0){
xchg(s,0); /*try changing the value - down*/
if (*s != 0){ /*no success in changing*/
if (raise(SIGALRM)!=0){ /*raise returns 0 on success*/
return -1;
}
} else{
flag = 1;
return 0;
}
}
}
#endif
Any ideas?

You declare the main function to be static. That means it will have internal linkage, and not be exported. Therefore the linker will not be able to find it.
Remove the static modifier and that should work.
However there are also many other problems in your Makefile that will most likely lead to problems:
The target ph only depends on ph.c, not the libraries you want to link with. You need to add the libraries as dependencies as well (their full file-names).
You should also make the libraries depend on the object files used to create them, and rely on the implicit rules to build object files from source files.
Also note that for a library named A then the file-name needs to be libA.a, otherwise the linker will not be able to find it with the -l (lower-case L) option.

The error message is pretty clear:
(.text+0x18): undefined reference to `main'
In ph.c you have (probably accustomed from Java?):
static int main(int argc, char *argv[])
In C:
static ([CPPReference]: C keywords: static) means internal linkage (only valid in current translation unit (ph.o in our case)), so a static symbol won't be "seen" by the linker (loader)
Code linked to an application (not a dynamic library or shared object (.so: linked with -shared)), must explicitly define main, and that should be visible by the linker
The combination of the above 2, lead to what you're experiencing.
Remove the static and you'll be fine.
Note: There might be other errors in the Makefile.

Related

Printf with multiple threads (for real-time logging) in C

I have written a code for real-time logging. Here's the pseudo-code:
initialize Q; //buffer structure stores values to be printed
log(input)
{
push input to Q;
}
printLog() //infinte loop
{
loop(1)
{
if(Q is not empty)
{
values = pop(Q);
msg = string(values); //formating values into a message string
print(msg);
}
}
}
mainFunction()
{
loop(1)
{
/*
insert operations to be performed
*/
log(values); //log function called
}
}
main()
{
Create 4 threads; //1 mainFunction and 3 printLog
Bind them to CPUs;
}
I'm using atomic operations instead of locks.
When I print the output to the console, I see that each thread prints consecutively for a while. This must mean that once a thread enters printLog(), the other threads are inactive for a while.
What I want instead is while one thread is printing, another thread formats the next value popped from Q and prints it right after. How can this be achieved?
EDIT: I've realized the above information isn't sufficient. Here are some other details.
Buffer structure Q is a circular array of fixed size.
Pushing information to Q is faster than popping+printing. So by the time the Buffer structure is full, I want most of the information to be printed.
NOTE: mainFunction thread shouldn't wait to fill Buffer when it is full.
I'm trying to utilize all the threads at a given time. Currently, after one thread prints, the same thread reads and prints the next value (this means the other 2 threads are inactive).
Here's the actual code:
//use gcc main.c -o run -pthread
#define _GNU_SOURCE
#include <unistd.h>
#include <stdint.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
#include <math.h>
#include <signal.h>
#include <stdlib.h>
#define N 3
/* Buffer size */
#define BUFFER_SIZE 1000
struct values
{
uint64_t num;
char msg[20];
};
struct values Q[BUFFER_SIZE];
int readID = -1;
int writeID = -1;
int currCount = 0;
void Log(uint64_t n, char* m)
{
int i;
if (__sync_fetch_and_add(&currCount,1) < BUFFER_SIZE)
{
i = __sync_fetch_and_add(&writeID,1);
i = i%BUFFER_SIZE;
Q[i].num = n;
strcpy(Q[i].msg, m);
}
else __sync_fetch_and_add(&currCount,-1);
}
void *printLog(void *x)
{
int thID = *((int*)(x));
int i;
while(1)
{
if(__sync_fetch_and_add(&currCount,-1)>=0)
{
i = __sync_fetch_and_add(&readID,1);
i = i%BUFFER_SIZE;
printf("ThreadID: %2d, count: %10d, message: %15s\n",thID,Q[i].num,Q[i].msg);
}
else __sync_fetch_and_add(&currCount,1);
}
}
void *mainFunction()
{
uint64_t i = 0;
while(1)
{
Log(i,"Custom Message");
i++;
usleep(50);
}
}
int main()
{
/* Set main() Thread CPU */
cpu_set_t cpusetMain;
CPU_ZERO(&cpusetMain);
CPU_SET(0, &cpusetMain);
if(0 != pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpusetMain))
printf("pthread_setaffinity_np failed for CPU: 0\n");
int LogThID[N+1];
pthread_t LogThreads[N+1];
/* Create Threads */
if (pthread_create(&LogThreads[0], NULL, &mainFunction, NULL) != 0){return 0;}
for(int i=1; i<N+1 ; i++)
{
LogThID[i] = i;
if (pthread_create(&LogThreads[i], NULL, &printLog, &LogThID[i]) != 0){return i;}
}
/* Set CPUs */
cpu_set_t cpuset[N+1];
for(int i=0; i<N+1; i++)
{
CPU_ZERO(&cpuset[i]);
CPU_SET(i+1, &cpuset[i]);
if(0 != pthread_setaffinity_np(LogThreads[i], sizeof(cpu_set_t), &cpuset[i]))
printf("pthread_setaffinity_np failed for CPU: %d\n", i+1);
}
struct sched_param param[N+1];
for(int i=0; i<N+1; i++)
{
param[i].sched_priority = 91;
if(0 != pthread_setschedparam(LogThreads[i],SCHED_FIFO,&param[i]))
printf("pthread_setschedparam failed for CPU: %d\n", i);
}
/* Join threads */
for(int i=0; i<N+1; i++)
{
pthread_join(LogThreads[i], NULL);
}
return 0;
}

I have a function call in one program and this function is depreciated.Is there any newer version that I can use in my code | perf_buffer__new in ebpf

I have this function which is depreciated. First how one can find the new alternative to functions that are depreciated. the function exist in libbpf library and perf_buffer__new is the exact name. so basically as the name suggest its used to create perf buffer to share info between userspace and kernel. First I like to know is perf buffers are only specific to ebpf filters or not. not means I can use perf buffers in anything. for example if I have some driver code so I just add perf buffer to have info shared between some userspace app and the driver. so some searching on the web I found it specifically link to ebpf, is this true?
So this is my code that uses call to perf_buffer__new but that function is depreciated, this function in libbpf's libbpf.h header file declarations is commented out
So I like to new what is the new alternative that I can use in my code, if there is a change in api then i like to let u know that I am trying share buffer parameter in SEC("kprobe/__x64_sys_recvfrom") to userspace for that I have used PT_REGS_PARM2 and bpf_probe_read_kernel to and included the parameter in map data. So if api is changed then how to accomplish this this is my userspace and ebpf program
Userspace.c
// SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>
#include <signal.h>
#include <bpf/libbpf.h>
//create .o file root#this:/home/ubuntu/Desktop/ebpf/kern# clang -I /lib/modules/5.14.1/build -I /usr/include/bpf/ -O2 -Wall -c trace_output_user.c
static __u64 time_get_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec * 1000000000ull + ts.tv_nsec;
}
static __u64 start_time;
static __u64 cnt;
#define MAX_CNT 100000ll
static void print_bpf_output(void *ctx, int cpu, void *data, __u32 size)
{
struct {
int pid;
char cookie[90];
char *buf;
} *e = data;
int i=0;
printf("hello\n");
printf(" _____________________________________________________%d \n________%s\n",e->pid,e->buf);
i++;
//printf("received map value = %s\n",e->cookie);
/*if (e->cookie != 0x12345678) {
printf("BUG pid %llx cookie %d sized %d\n",
e->pid, e->cookie, size);
return;
}
cnt++;
if (cnt == MAX_CNT) {
printf("recv %lld events per sec\n",
MAX_CNT * 1000000000ll / (time_get_ns() - start_time));
return;
}*/
}
int main(int argc, char **argv)
{
struct perf_buffer_opts pb_opts = {};
struct bpf_link *link = NULL;
struct bpf_program *prog;
struct perf_buffer *pb;
struct bpf_object *obj;
int map_fd, ret = 0;
char filename[256];
FILE *f;
//snprintf(filename, sizeof(filename), "..o", argv[0]);
obj = bpf_object__open_file("./kprobe_send.o", NULL);
if (libbpf_get_error(obj)) {
fprintf(stderr, "ERROR: opening BPF object file failed\n");
return 0;
}
/* load BPF program */
if (bpf_object__load(obj)) {
fprintf(stderr, "ERROR: loading BPF object file failed\n");
goto cleanup;
}
map_fd = bpf_object__find_map_fd_by_name(obj, "my_map");
if (map_fd < 0) {
fprintf(stderr, "ERROR: finding a map in obj file failed\n");
goto cleanup;
}
printf("before\n");
prog = bpf_object__find_program_by_name(obj, "bpf_prog1");
if (libbpf_get_error(prog)) {
fprintf(stderr, "ERROR: finding a prog in obj file failed\n");
goto cleanup;
}
printf("after\n");
link = bpf_program__attach(prog);
printf("after\n");
if (libbpf_get_error(link)) {
fprintf(stderr, "ERROR: bpf_program__attach failed\n");
link = NULL;
goto cleanup;
}
printf("after\n");
pb_opts.sample_cb = print_bpf_output;
pb = perf_buffer__new_deprecated(map_fd, 8, &pb_opts);//error
printf("after\n");
ret = libbpf_get_error(pb);
if (ret) {
printf("failed to setup perf_buffer: %d\n", ret);
return 1;
}
f = popen("taskset 1 dd if=/dev/zero of=/dev/null", "r");
(void) f;
start_time = time_get_ns();
while ((ret = perf_buffer__poll(pb, 1000)) >= 0 && cnt < MAX_CNT) {
}
kill(0, SIGINT);
cleanup:
bpf_link__destroy(link);
bpf_object__close(obj);
return ret;
}
Kernel.c
#include <linux/ptrace.h>
#include <linux/version.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <string.h>
#include <sys/sendfile.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include </usr/include/bpf/bpf_tracing.h>
#include <linux/seccomp.h>
#define RAND_MAX 0x7fff
#define PERF_SAMPLE_RAW 1U << 0
#define randrange(N) rand() / (RAND_MAX/(N) + 1)
#define MAX 100000000 /* Values will be in the range (1 .. MAX) */
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(int));
__uint(value_size, sizeof(int));
__uint(max_entries, 100);
} my_map SEC(".maps");
SEC("kprobe/__x64_sys_recvfrom")
int bpf_prog1(struct pt_regs *ctx)
{
static int vektor[100000000];
int candidates[MAX];
int i;
long key;
//srand(time(NULL)); /* Seed the random number generator. */
/*for (i=0; i<MAX; i++)
candidates[i] = i;
for (i = 0; i < MAX-1; i++) {
int c = randrange(MAX-i);
int t = candidates[i];
candidates[i] = candidates[i+c];
candidates[i+c] = t;
}
for (i=0; i<10; i++)
vektor[i] = candidates[i] + 1;*/
struct S {
int pid;
char cookie[90];
char *ptr;
} data={1,""};
//char *ptr = PT_REGS_PARM2(ctx);
struct seccomp_data sd;
bpf_probe_read_kernel(&sd, sizeof(sd), (void *)PT_REGS_PARM2(ctx));
if (sd.args[2] > 128 && sd.args[2] <= 1024) {
char fmt[] = "read(fd=%d, buf=%p, size=%d)\n";
bpf_trace_printk(fmt, sizeof(fmt),
sd.args[0], sd.args[1], sd.args[2]);
data.ptr=(char *)sd.args[1];
// memcpy(data.ptr,sd.args[1],sizeof(char)*220);
}
//data.pid =count;// bpf_get_current_pid_tgid();
//if(buf==NULL)
//memcpy(data.cookie,buf,20);
//data.ptr=ptr;
// data.cookie[0]=buf[0];
//bpf_get_current_comm(&data.cookie, sizeof(data.cookie));
//key=vektor[i];
//bpf_map_update_elem(fd,&key,&data,BPF_ANY);
//bpf_perf_event_output(ctx, &my_map, 1, &data, sizeof(data));
return 0;
}
char _license[] SEC("license") = "GPL";
int _version SEC("version") = 99;
when I compile and link the program userspace with root#this:/home/ubuntu/Desktop/ebpf/Linux-exFilter-main/pkg/probe/bpf# clang -v trace_output_user.c -o trace -lbpf
I get error that and warning
trace_output_user.c:101:7: warning: 'perf_buffer__new_deprecated' is deprecated: libbpf v0.7+: use new variant of perf_buffer__new() instead [-Wdeprecated-declarations]
pb = perf_buffer__new_deprecated(map_fd, 8, &pb_opts);
^
/usr/include/bpf/libbpf.h:949:12: note: 'perf_buffer__new_deprecated' has been explicitly marked deprecated here
LIBBPF_API LIBBPF_DEPRECATED_SINCE(0, 7, "use new variant of perf_buffer__new() instead")
^
/usr/include/bpf/libbpf_common.h:24:4: note: expanded from macro 'LIBBPF_DEPRECATED_SINCE'
(LIBBPF_DEPRECATED("libbpf v" # major "." # minor "+: " msg))
^
/usr/include/bpf/libbpf_common.h:19:47: note: expanded from macro 'LIBBPF_DEPRECATED'
#define LIBBPF_DEPRECATED(msg) __attribute__((deprecated(msg)))
^
1 warning generated.
"/usr/bin/ld" -z relro --hash-style=gnu --build-id --eh-frame-hdr -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 -o trace /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/crt1.o /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/crti.o /usr/bin/../lib/gcc/x86_64-linux-gnu/10/crtbegin.o -L/usr/bin/../lib/gcc/x86_64-linux-gnu/10 -L/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu -L/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../lib64 -L/lib/x86_64-linux-gnu -L/lib/../lib64 -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib64 -L/usr/lib/x86_64-linux-gnu/../../lib64 -L/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../.. -L/usr/lib/llvm-12/bin/../lib -L/lib -L/usr/lib /tmp/trace_output_user-ec780e.o -lbpf -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/bin/../lib/gcc/x86_64-linux-gnu/10/crtend.o /usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../x86_64-linux-gnu/crtn.o
/usr/bin/ld: /tmp/trace_output_user-ec780e.o: in function `main':
trace_output_user.c:(.text+0x1e2): undefined reference to `perf_buffer__new_deprecated'
some details
perf_buffer__new_deprecated
and
perf_buffer__new are depreciated in latest version of libbpf
My kernel version is 5.14.1
1. you are explicitly using perf_buffer__new_deprecated in your code - don't do this: Use perf_buffer_new instead. You should never call a function that already has 'deprecated' in it's name.
2. Take a look in the header:
libbpf/libbpf.h
perf_buffer_new is defined like this:
#define perf_buffer__new(...) ___libbpf_overload(___perf_buffer_new, __VA_ARGS__)
#define ___perf_buffer_new6(map_fd, page_cnt, sample_cb, lost_cb, ctx, opts) \
perf_buffer__new(map_fd, page_cnt, sample_cb, lost_cb, ctx, opts)
#define ___perf_buffer_new3(map_fd, page_cnt, opts) \
perf_buffer__new_deprecated(map_fd, page_cnt, opts)
So there are 2 functions:
Old: pef_buffer_new with 3 arguments
New: perf_buffer_new with 6 arguments.
With the macros, libbpf makes old code compile, too, while telling you to change your function call.
You are using the old version right now (with 3 arguments). Switch to the new version with 6 arguments, as the 3-arguments-variant will be removed.
The new function (see libbpf/libbpf.h):
/**
* #brief **perf_buffer__new()** creates BPF perfbuf manager for a specified
* BPF_PERF_EVENT_ARRAY map
* #param map_fd FD of BPF_PERF_EVENT_ARRAY BPF map that will be used by BPF
* code to send data over to user-space
* #param page_cnt number of memory pages allocated for each per-CPU buffer
* #param sample_cb function called on each received data record
* #param lost_cb function called when record loss has occurred
* #param ctx user-provided extra context passed into *sample_cb* and *lost_cb*
* #return a new instance of struct perf_buffer on success, NULL on error with
* *errno* containing an error code
*/
LIBBPF_API struct perf_buffer *
perf_buffer__new(int map_fd, size_t page_cnt,
perf_buffer_sample_fn sample_cb, perf_buffer_lost_fn lost_cb, void *ctx,
const struct perf_buffer_opts *opts);
You can find the definitions for sample_cb and lost_cb in the header as well:
From above, we know sample_cb has the type perf_buffer_sample_fn. For the other callback, it is similar.
Both are defined in libbpf.h:
typedef void (*perf_buffer_sample_fn)(void *ctx, int cpu,
void *data, __u32 size);
typedef void (*perf_buffer_lost_fn)(void *ctx, int cpu, __u64 cnt);
See libbpf/libbpf.h
So a valid callback function could be
void myCallbackForNewData(void* ctx, int cpu, void*data, __u32 size) {}
Be aware that ctx* has nothing to do with BPF - it is something you can freely define in perf_buffer__new. This is useful if you use the same handler for multiple perf_buffers. Otherwise, you can just enter NULL.

Is there a way to find a list of pthread_mutexes held by the current pthread?

I have tried creating a wrapper over pthread_mutex that maintains a list of all mutexes held by the current thread. However, I also utilize certain library code, for which, I can't possibly use this wrapper. Is there any way to do get this list for generic pthread_mutexes?
FWIW, I am using a Linux Host.
You can interpose (via LD_PRELOAD and <dlfcn.h>, for example) the pthread_mutex_lock(), pthread_mutex_trylock(), and pthread_mutex_unlock() functions with your own that call the underlying functions, but also maintain a per-thread array/chain of held mutex addresses. (You can use the __thread storage class keyword provided by GCC, equivalent to the C11 _Thread_local, to declare per-thread variables.)
Here is a verified working example.
Makefile, to build the interposing library and the example program:
# SPDX-License-Identifier: CC0-1.0
CC := gcc
CFLAGS := -Wall -Wextra -O2
LDFLAGS := -pthread -ldl
BINS := libheld.so example
.PHONY: all clean run
all: clean $(BINS)
clean:
rm -f *.o $(BINS)
run: libheld.so example
env LD_PRELOAD=./libheld.so ./example
%.o: %.c
$(CC) $(CFLAGS) -c $^
libheld.so: libheld.c
$(CC) $(CFLAGS) -fPIC $^ -shared -Wl,-soname,$# -ldl -o $#
example: example.o held.o
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $#
Note that the indentation in Makefiles must use Tabs, so if you copy-paste the above, you will need to fix the indentation, for example by running sed -e 's|^ *|\t|' -i Makefile.
libheld.c, the implementation of the interposing library (libheld.so):
// SPDX-License-Identifier: CC0-1.0
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <dlfcn.h>
#include <errno.h>
static int (*original_lock)(pthread_mutex_t *) = NULL;
static int (*original_trylock)(pthread_mutex_t *) = NULL;
static int (*original_unlock)(pthread_mutex_t *) = NULL;
static int held_err = 0;
static __thread size_t held_max = 0;
static __thread size_t held_num = 0;
static __thread pthread_mutex_t **held_ptr = NULL;
int libheld_errno(void)
{
return __atomic_load_n(&held_err, __ATOMIC_SEQ_CST);
}
size_t libheld_count(void)
{
return held_num;
}
pthread_mutex_t *libheld_mutex(size_t i)
{
return (i < held_num) ? held_ptr[i] : NULL;
}
int pthread_mutex_lock(pthread_mutex_t *m)
{
int (*lock)(pthread_mutex_t *);
__atomic_load(&original_lock, &lock, __ATOMIC_SEQ_CST);
if (!lock) {
lock = dlsym(RTLD_NEXT, "pthread_mutex_lock");
if (!lock) {
__atomic_store_n(&held_err, ENOSYS, __ATOMIC_SEQ_CST);
return ENOSYS;
}
__atomic_store(&original_lock, &lock, __ATOMIC_SEQ_CST);
}
int retval = lock(m);
if (retval)
return retval;
if (held_num >= held_max) {
const int saved_errno = errno;
const size_t temp_max = (held_num | 127) + 121;
void *temp_ptr;
temp_ptr = realloc(held_ptr, temp_max * sizeof held_ptr[0]);
if (!temp_ptr) {
__atomic_store_n(&held_err, ENOMEM, __ATOMIC_SEQ_CST);
errno = saved_errno;
return 0;
}
held_max = temp_max;
held_ptr = temp_ptr;
}
held_ptr[held_num++] = m;
return 0;
}
int pthread_mutex_trylock(pthread_mutex_t *m)
{
int (*trylock)(pthread_mutex_t *);
__atomic_load(&original_trylock, &trylock, __ATOMIC_SEQ_CST);
if (!trylock) {
trylock = dlsym(RTLD_NEXT, "pthread_mutex_trylock");
if (!trylock) {
__atomic_store_n(&held_err, ENOSYS, __ATOMIC_SEQ_CST);
return ENOSYS;
}
__atomic_store(&original_trylock, &trylock, __ATOMIC_SEQ_CST);
}
int retval = trylock(m);
if (retval)
return retval;
if (held_num >= held_max) {
const int saved_errno = errno;
const size_t temp_max = (held_num | 127) + 121;
void *temp_ptr;
temp_ptr = realloc(held_ptr, temp_max * sizeof held_ptr[0]);
if (!temp_ptr) {
__atomic_store_n(&held_err, ENOMEM, __ATOMIC_SEQ_CST);
errno = saved_errno;
return 0;
}
held_max = temp_max;
held_ptr = temp_ptr;
}
held_ptr[held_num++] = m;
return 0;
}
int pthread_mutex_unlock(pthread_mutex_t *m)
{
int (*unlock)(pthread_mutex_t *);
__atomic_load(&original_unlock, &unlock, __ATOMIC_SEQ_CST);
if (!unlock) {
unlock = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
if (!unlock) {
__atomic_store_n(&held_err, ENOSYS, __ATOMIC_SEQ_CST);
return ENOSYS;
}
__atomic_store(&original_unlock, &unlock, __ATOMIC_SEQ_CST);
}
int retval = unlock(m);
if (retval)
return retval;
size_t i = 0;
while (i < held_num) {
if (held_ptr[i] == m) {
held_num--;
if (i < held_num) {
memmove(held_ptr + i, held_ptr + i + 1, (held_num - i) * sizeof held_ptr[0]);
}
} else {
i++;
}
}
return 0;
}
held.c, implementing the interfaces needed to obtain the mutex tracking information (whether mutex tracking is available, how many mutexes the current thread holds, and the addresses of those mutexes):
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <pthread.h>
#include <dlfcn.h>
#include <errno.h>
static int libheld_errno_default(void)
{
return ENOSYS;
}
static size_t libheld_count_default(void)
{
return 0;
}
static pthread_mutex_t *libheld_mutex_default(size_t i)
{
(void)i; /* Silences warning about unused parameter; generates no code. */
return NULL;
}
static int (*held_errno_func)(void) = NULL;
static size_t (*held_count_func)(void) = NULL;
static pthread_mutex_t *(*held_mutex_func)(size_t) = NULL;
int held_errno(void)
{
int (*errno_func)(void);
__atomic_load(&held_errno_func, &errno_func, __ATOMIC_SEQ_CST);
if (!held_errno_func) {
errno_func = dlsym(RTLD_DEFAULT, "libheld_errno");
if (!errno_func)
errno_func = libheld_errno_default;
__atomic_store(&held_errno_func, &errno_func, __ATOMIC_SEQ_CST);
}
return errno_func();
}
size_t held_count(void)
{
size_t (*count_func)(void);
__atomic_load(&held_count_func, &count_func, __ATOMIC_SEQ_CST);
if (!count_func) {
count_func = dlsym(RTLD_DEFAULT, "libheld_count");
if (!count_func)
count_func = libheld_count_default;
__atomic_store(&held_count_func, &count_func, __ATOMIC_SEQ_CST);
}
return count_func();
}
pthread_mutex_t *held_mutex(size_t i)
{
pthread_mutex_t *(*mutex_func)(size_t);
__atomic_load(&held_mutex_func, &mutex_func, __ATOMIC_SEQ_CST);
if (!mutex_func) {
mutex_func = dlsym(RTLD_DEFAULT, "libheld_mutex");
if (!mutex_func)
mutex_func = libheld_mutex_default;
__atomic_store(&held_mutex_func, &mutex_func, __ATOMIC_SEQ_CST);
}
return mutex_func(i);
}
Note that because we won't know until runtime whether mutex tracking is available and the interposing library loaded, we need to use <dlfcn.h> to obtain the functions provided by the interposing library itself. (If we compiled held.c into a library, then we could just implement the default functions here, and let the interposing library interpose them too.)
This way, it suffices to link the target binary against libld (-ldl) too.
held.h, declaring the interfaces provided by the above held.c:
// SPDX-License-Identifier: CC0-1.0
#ifndef HELD_H
#define HELD_H
#include <stdlib.h>
#include <pthread.h>
/* Returns zero if mutex tracking is available, nonzero otherwise. */
extern int held_errno(void);
/* Returns the number of mutexes held by the current thread. */
extern size_t held_count(void);
/* Returns a pointer to the i'th mutex (0 up to held_count()-1) held by the current thread. */
extern pthread_mutex_t *held_mutex(size_t);
#endif /* HELD_H */
example.c, showing an example of tracking held mutexes:
// SPDX-License-Identifier: CC0-1.0
#define _POSIX_C_SOURCE 200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include "held.h"
#ifndef LOCKS
#define LOCKS 5
#endif
void print_held(FILE *out)
{
if (!out)
out = stdout;
int err = held_errno();
if (err == ENOSYS) {
fprintf(out, "Mutex tracking is not available.\n");
return;
} else
if (err) {
fprintf(out, "Error in mutex tracking: %s.\n", strerror(err));
return;
}
size_t n = held_count();
if (n > 0) {
fprintf(out, "%zu %s held by current thread:\n", n, (n > 1) ? "mutexes" : "mutex");
for (size_t i = 0; i < n; i++) {
fprintf(out, "\t%p\n", held_mutex(i));
}
} else {
fprintf(out, "No mutexes held by current thread.\n");
}
}
int main(void)
{
pthread_mutex_t lock[LOCKS];
size_t i;
for (i = 0; i < LOCKS; i++)
pthread_mutex_init(lock + i, NULL);
for (i = 0; i < LOCKS; i++)
pthread_mutex_lock(lock + i);
print_held(stdout);
for (i = 0; i < LOCKS; i++)
pthread_mutex_unlock(lock + i);
print_held(stdout);
return EXIT_SUCCESS;
}
Compile the library and the example program by running make clean all.
If you run the example, ./example, it just outputs "Mutex tracking is not available."
If you run the example with the interposing library, LD_PRELOAD=./libheld.so ./example, it outputs the (default 5) mutexes the main thread holds.
In case you wonder about the SPDX-License-Identifier comments, they this code is licensed under Creative Commons Zero v1.0 Universal license. It means you can do anything you want with this code, but I provide no guarantees whatsoever.
An even easier external method is to run the unmodified binary under ltrace, i.e. ltrace -fttt -o logfile -e pthread_mutex_lock+pthread_mutex_trylock+pthread_mutex_unlock ./example
so that ltrace logs each pthread_mutex_lock, pthread_mutex_trylock, and pthread_mutex_unlock call to the logfile with a timestamp (seconds since Unix epoch).

Is it possible to access/update the child thread's resource from parent thread?

I'm doing a socket programming in C and I'm completely new to Multithreading.
This is my scenario, I need a parent thread which reads the data from socket(lets say..) and enqueue the same to the queue of its child thread.The problem here is, how can I update the queue which is child thread-specific from the parent thread.
Thread-safe queue that supports multiple producers and consumers.
MtQueue.h:
#ifndef MtQueue_H
#define MtQueue_H
#include <pthread.h>
#include <stdlib.h>
// A fixed-size circular buffer.
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
int done;
int empty;
int full;
size_t max;
size_t next_insert;
size_t next_read;
void** buf;
} MtQueue;
// Returns NULL and sets errno on error.
// Free the queue with MtQueue_delete when done.
MtQueue* MtQueue_new(size_t max);
// Returns 0 and sets errno on error.
// Destroy the queue with MtQueue_destroy when done.
int MtQueue_init(MtQueue* q, size_t max);
// Inverse of MtQueue_new.
// Only call when the queue is no longer in use.
void MtQueue_delete(MtQueue* q);
// Inverse of MtQueue_init.
// Only call when the queue is no longer in use.
void MtQueue_destroy(MtQueue* q);
// Initiates shutdown of the queue.
// You must ensure that no there are no pending call to enqueue before this is called.
// You must ensure not to call enqueue once this is called.
void MtQueue_done(MtQueue* q);
// Returns the oldest item from the queue (via a parameter) and returns 1.
// If the queue is empty and done, returns 0.
// If the queue is empty and not done, waits until that changes.
int MtQueue_dequeue(MtQueue* q, void** pp);
// Adds the argument to the queue.
// If the queue is full, waits until that changes.
void MtQueue_enqueue(MtQueue* q, void* p);
#endif
MtQueue.c:
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include "MtQueue.h"
MtQueue* MtQueue_new(size_t max) {
MtQueue* q = malloc(sizeof(MtQueue));
if (!q)
goto Error1;
if (!MtQueue_init(q, max))
goto Error2;
return q;
Error2:
free(q);
Error1:
return NULL;
}
int MtQueue_init(MtQueue* q, size_t max) {
void** buf = malloc(sizeof(void*) * max);
if (!buf)
goto Error1;
errno = pthread_mutex_init(&(q->mutex), NULL);
if (errno)
goto Error2;
errno = pthread_cond_init(&(q->cond), NULL);
if (errno)
goto Error3;
q->done = 0;
q->empty = 1;
q->full = 0;
q->max = max;
q->next_insert = 0;
q->next_read = 0;
q->buf = buf;
return 1;
Error3:
pthread_mutex_destroy(&(q->mutex));
Error2:
free(buf);
Error1:
return 0;
}
void MtQueue_delete(MtQueue* q) {
MtQueue_destroy(q);
free(q);
}
void MtQueue_destroy(MtQueue* q) {
assert(q->empty);
free(q->buf);
pthread_cond_destroy(&(q->cond));
pthread_mutex_destroy(&(q->mutex));
}
void MtQueue_done(MtQueue* q) {
pthread_mutex_lock(&(q->mutex));
q->done = 1;
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}
int MtQueue_dequeue(MtQueue* q, void** pp) {
pthread_mutex_lock(&(q->mutex));
while (q->empty && !q->done)
pthread_cond_wait(&(q->cond), &(q->mutex));
int dequeued;
if (q->empty) {
// q->done && q->empty is true.
// We are completely done.
dequeued = 0;
} else {
*pp = q->buf[ q->next_read ];
q->next_read = ( q->next_read + 1 ) % q->max;
q->empty = q->next_read == q->next_insert;
q->full = 0;
dequeued = 1;
}
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
return dequeued;
}
void MtQueue_enqueue(MtQueue* q, void* p) {
pthread_mutex_lock(&(q->mutex));
while (q->full)
pthread_cond_wait(&(q->cond), &(q->mutex));
assert(!q->done);
q->buf[q->next_insert] = p;
q->next_insert = ( q->next_insert + 1 ) % q->max;
q->empty = 0;
q->full = q->next_insert == q->next_read;
pthread_cond_signal(&(q->cond));
pthread_mutex_unlock(&(q->mutex));
}
a.c (Example user):
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "MtQueue.h"
// Producers will block if there are this many items in the queue.
#ifndef QUEUE_SIZE
#define QUEUE_SIZE 10
#endif
// The number of consumers (worker threads) to create.
#ifndef NUM_WORKERS
#define NUM_WORKERS 4
#endif
// The amount of work to generate for this test.
#ifndef NUM_JOBS
#define NUM_JOBS 40
#endif
// Simulate work using a sleep.
#ifndef SIM_WORK
#define SIM_WORK 0
#endif
#if SIM_WORK
static int msleep(long msec) {
struct timespec ts;
int res;
if (msec < 0) {
errno = EINVAL;
return -1;
}
ts.tv_sec = msec / 1000;
ts.tv_nsec = (msec % 1000) * 1000000;
do {
res = nanosleep(&ts, &ts);
} while (res && errno == EINTR);
return res;
}
#endif
// Shared variables.
static MtQueue q;
static void* worker_func(void* worker_id_) {
uintptr_t worker_id = (uintptr_t)worker_id_;
#if SIM_WORK
unsigned int seed = worker_id; // Whatever.
#endif
uintptr_t j;
while (MtQueue_dequeue(&q, (void**)&j)) {
printf("[%" PRIuPTR "] Dequeued %" PRIuPTR "\n", worker_id, j);
#if SIM_WORK
msleep( rand_r(&seed) % 1000 + 1000 ); // Simulate a 1 to 2s load.
#endif
printf("[%" PRIuPTR "] Finished processing %" PRIuPTR "\n", worker_id, j);
}
return NULL;
}
int main(void) {
MtQueue_init(&q, QUEUE_SIZE);
pthread_t workers[NUM_WORKERS];
for (uintptr_t w=0; w<NUM_WORKERS; ++w) {
if (errno = pthread_create(&(workers[w]), NULL, worker_func, (void*)w)) {
perror(NULL);
exit(1);
}
}
for (uintptr_t j=0; j<NUM_JOBS; ++j) {
printf("[x] Enqueuing %" PRIuPTR "...\n", j);
MtQueue_enqueue(&q, (void*)j);
printf("[x] Enqueued %" PRIuPTR ".\n", j);
}
MtQueue_done(&q);
printf("[x] Called done.\n");
for (uintptr_t w=0; w<NUM_WORKERS; ++w)
pthread_join(workers[w], NULL);
MtQueue_destroy(&q);
return 0;
}
How to run example user:
gcc -Wall -Wextra -pedantic a.c MtQueue.c -o a -lpthread && ./a
gcc -D SIM_WORK=1 -D NUM_JOBS=20 -Wall -Wextra -pedantic a.c MtQueue.c -o a -pthread && ./a

main doesn't continue after call pthread_join function

I'm new to pthread and multithreading, i have written a code like that.
#include <pthread.h>
#include <unistd.h>
void *nfc_read(void *arg)
{
int fd = -1;
int ret;
uint8_t read_data[24];
while(1){
ret = read_block(fd, 8, read_data);
if(!ret){
return (void)read_data;
}
}
}
int main(int argc, char *argv[])
{
pthread_t my_thread;
void *returnValue;
pthread_create(&my_thread, NULL, nfc_read, NULL);
pthread_join(my_thread, &returnValue);
printf("NFC card value is : %s \n", (char)returnValue);
printf("other process");
return 0;
}
Until the return value from nfc_read function, as I will have other code I need to run and I don't want to block in main. How can i do that?
This is a sample where a read thread runs concurrently to the "main" thread which is doing other work (in this case, printing dots and sleeping).
To keep things simple, a simulated the reading from an input device with copying a constant string character by character. I guess, this is reasonable as the synchronization of threads is focused.
For the synchronization of threads, I used atomic_bool with atomic_store() and atomic_load() which are provided by the Atomic Library (since C11).
My sample application test-concurrent-read.c:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdatomic.h>
#include <unistd.h>
/* sampe input */
const char sampleInput[]
= "This is sample input which is consumed as if it was read from"
" a (very slow) external device.";
atomic_bool readDone = ATOMIC_VAR_INIT(0);
void* threadRead(void *pArg)
{
char **pPBuffer = (char**)pArg;
size_t len = 0, size = 0;
int c; const char *pRead;
for (pRead = sampleInput; (c = *pRead++) > 0; sleep(1)) {
if (len + 1 >= size) {
if (!(*pPBuffer = realloc(*pPBuffer, (size + 64) * sizeof(char)))) {
fprintf(stderr, "ERROR! Allocation failed!\n");
break;
}
size += 64;
}
(*pPBuffer)[len++] = c; (*pPBuffer)[len] = '\0';
}
atomic_store(&readDone, 1);
return NULL;
}
int main()
{
/* start thread to read concurrently */
printf("Starting thread...\n");
pthread_t idThreadRead; /* thread ID for read thread */
char *pBuffer = NULL; /* pointer to return buffer from thread */
if (pthread_create(&idThreadRead, NULL, &threadRead, &pBuffer)) {
fprintf(stderr, "ERROR: Failed to start read thread!\n");
return -1;
}
/* start main loop */
printf("Starting main loop...\n");
do {
putchar('.'); fflush(stdout);
sleep(1);
} while (!atomic_load(&readDone));
putchar('\n');
void *ret;
pthread_join(idThreadRead, &ret);
/* after sync */
printf("\nReceived: '%s'\n", pBuffer ? pBuffer : "<NULL>");
free(pBuffer);
/* done */
return 0;
}
Compiled and tested with gcc in cygwin on Windows 10 (64 bit):
$ gcc -std=c11 -pthread -o test-concurrent-read test-concurrent-read.c
$ ./test-concurrent-read
Starting thread...
Starting main loop...
.............................................................................................
Received: 'This is sample input which is consumed as if it was read from a (very slow) external device.'
$
I guess, it is worth to mention why there is no mutex guarding for pBuffer which is used in main() as well as in threadRead().
pBuffer is initialized in main() before pthread_create() is called.
While thread_read() is running, pBuffer is used by it exclusively (via its passed address in pPBuffer).
It is accessed in main() again but not before pthread_join() which grants that threadRead() has ended.
I tried to find a reference by google to confirm that this procedure is well-defined and reasonable. The best, I could find was SO: pthread_create(3) and memory synchronization guarantee in SMP architectures which cites The Open Group Base Specifications Issue 7 - 4.12 Memory Synchronization.

Resources