Getting physical address from /proc/[pid]/pagemap fails - c

I am trying to get physical address from /proc/[pid]/pagemap using virtual address and I thought it was working fine until I tried with a simple test program.
This is my code which gets physical address using virtual address:
#include "addresstranslation.h"
#include <stdio.h>
#define PAGEMAP_ENTRY 8
#define GET_BIT(X,Y) (X & ((uint64_t)1<<Y)) >> Y
#define GET_PFN(X) X & 0x7FFFFFFFFFFFFF
#define page_mapping_file "/proc/self/pagemap"
const int __endian_bit = 1;
#define is_bigendian() ( (*(char*)&__endian_bit) == 0 )
uintptr_t virtual_to_physical_address(uintptr_t virt_addr)
{
uintptr_t file_offset = 0;
uintptr_t read_val = 0;
uintptr_t page_number = 0;
int i = 0;
int c = 0;
int pid = 0;
int status = 0;
unsigned char c_buf[PAGEMAP_ENTRY];
FILE *f = fopen(page_mapping_file, "rb");
if(!f)
{
// if this happens run as root
printf("Error! Cannot open %s. Please, run as root.\n", page_mapping_file);
return 0;
}
file_offset = virt_addr / getpagesize() * PAGEMAP_ENTRY;
status = fseek(f, file_offset, SEEK_SET);
if(status)
{
printf("Error! Cannot seek in %s.\n", page_mapping_file);
perror("Failed to do fseek!");
fclose(f);
return 0;
}
for(i = 0; i < PAGEMAP_ENTRY; i++)
{
c = getc(f);
if(c == EOF)
{
fclose(f);
return 0;
}
if(is_bigendian())
{
c_buf[i] = c;
}
else
{
c_buf[PAGEMAP_ENTRY - i - 1] = c;
}
}
for(i=0; i < PAGEMAP_ENTRY; i++)
{
read_val = (read_val << 8) + c_buf[i];
}
/*
if(GET_BIT(read_val, 63))
{
page_number = GET_PFN(read_val);
printf("%d \n", page_number);
}
else
{
printf("Page not present\n");
}
if(GET_BIT(read_val, 62))
{
printf("Page swapped\n");
}
*/
fclose(f);
return read_val;
}
addresstranslation.h:
/*
* addresstranslation.h
*
* Translates virtual to physical address.
*/
#ifndef __ADDRESS_TRANSLATION_H
#define __ADDRESS_TRANSLATION_H
#include <inttypes.h>
#include <stdint.h>
uintptr_t virtual_to_physical_address(uintptr_t virt_addr);
#endif
And this is the simple test that I tried.
#include <stdlib.h>
#include <stdio.h>
#include "addresstranslation.h"
int main(int argc, char* argv[])
{
int *a1, *a2, *b1,*c1, *b2 = NULL;
int N = 4096;
uintptr_t ap1, ap2, bp1, bp2 = 0;
printf("Test virtual to physical address translation.\n");
a1 = (int*)malloc(sizeof(int) * N);
if (!a1)
{
printf("Error: cannot allocate memory for a\n");
return 1;
}
b1 = (int*)malloc(sizeof(int) * N);
if (!b1)
{
printf("Error: cannot allocate memory for b\n");
return 1;
}
ap1 = virtual_to_physical_address((uintptr_t)a1);
bp1 = virtual_to_physical_address((uintptr_t)b1);
printf("a1_virt= %p: a1_phys= %" PRIxPTR "\n", a1, ap1);
printf("b1_virt= %p b1_phys= %" PRIxPTR "\n", b1, bp1);
a2 = a1 + 1000;
b2 = b1 + 1;
ap2 = virtual_to_physical_address((uintptr_t)a2);
bp2 = virtual_to_physical_address((uintptr_t)b2);
printf("a2_virt= %p a2_phys= %" PRIxPTR "\n", a2, ap2);
printf("b2_virt= %p b2_phys= %" PRIxPTR "\n", b2, bp2);
printf("Done\n");
}
Which prints something like this:
Test virtual to physical address translation.
a1_virt= 0x958d008: a1_phys= 4f8ce
b1_virt= 0x9591010 b1_phys= 4d40b
a2_virt= 0x958dfa8 a2_phys= 4f8ce
b2_virt= 0x9591014 b2_phys= 4d40b
Done
As you can see a1 and a2 have different virtual addresses but have same physical address, my question is; Is my virtual-physical address conversion faulty or is it about Linux memory management and it is possible one physical address can be mapped to two different virtual addresses ?

the C language is case sensitive.
so this macro:
#DEFINE PAGE_MAPPING_FILE "/PROC/self/pagemap"
is not invoked when using:
FILE *f = fopen(page_mapping_file, "rb");
and #DEFINE means nothing to the C compiler.
The correct spelling is: #define I.E. all lower case

Related

How to Combine 2 Struct arrays in C

iv tried a lot of solutions to try to get this working (i.e using memcpy etc) I cant seem to find the issue, depending on what I try I either end up with gibberish or SEGV
iv spent a lot of time already googling and trying different ways, i still cant figure out why the arrays won't combine successfully
#include <stdio.h>
#include <stdint.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/syscall.h>
#include <unistd.h>
#define log_info printf
typedef struct
{
char* name;
//size_t size;
} entry_t;
/* qsort struct comparison function (C-string field) */
static int struct_cmp_by_name(const void* a, const void* b)
{
entry_t* ia = (entry_t*)a;
entry_t* ib = (entry_t*)b;
return strcmp(ia->name, ib->name);
/* strcmp functions works exactly as expected from comparison function */
}
entry_t* get_item_entries(const char* dirpath, int* count)
{
struct dirent* dent;
char buffer[512]; // fixed buffer
int dfd = 0,
n, r = 1; // item counter, rounds to loop
entry_t* p = NULL; // we fill this struct with items
loop:
n = 0;
printf("loop: %d, count:%d\n", r, *count);
// try to open dir
dfd = open(dirpath, O_RDONLY, 0);
if (dfd < 0)
{
printf("Invalid directory. (%s)\n", dirpath);
*count = -1;
return NULL;
}
else
{
printf("open(%s)\n", dirpath);
}
memset(buffer, 0, sizeof(buffer));
while (syscall(SYS_getdents, dfd, buffer, sizeof(buffer)) != 0)
{
dent = (struct dirent*)buffer;
while (dent->d_fileno)
{ // skip `.` and `..`
if (!strncmp(dent->d_name, "..", 2)
|| !strncmp(dent->d_name, ".", 1)) goto skip_dent;
// deal with filtering outside of this function, we just skip .., .
switch (r)
{ // first round: just count items
case 1:
{
// skip special cases
if (dent->d_fileno == 0) goto skip_dent;
break;
}
// second round: store filenames
case 0: p[n].name = strdup(dent->d_name); break;
}
n++;
skip_dent:
dent = (struct dirent*)((void*)dent + dent->d_reclen);
if (dent == (void*)&buffer[512]) break; // refill buffer
}
memset(buffer, 0, sizeof(buffer));
}
close(dfd);
// on first round, calloc for our list
if (!p)
{ // now n holds total item count, note it
p = calloc(n, sizeof(entry_t));
*count = n;
}
// first round passed, loop
r--; if (!r) goto loop;
// report count
printf("%d items at %p, from 1-%d\n", *count, (void*)p, *count);
/* resort using custom comparision function */
qsort(p, *count, sizeof(entry_t), struct_cmp_by_name);
// report items
//for (int i = 0; i < num; ++i) log_error( "%s", p[i].name);
return p;
}
int main(int argc, char* argv[])
{
int HDD_count = -1;
uint32_t total = -1;
int ext_count = -1;
entry_t* e = NULL;
entry_t *HDD = get_item_entries("/mnt/f/n", &HDD_count);
entry_t* ext = get_item_entries("/mnt/f/dls", &ext_count);
total = ext_count + HDD_count;
e = (entry_t*)malloc(sizeof *e * total);
if (e != NULL)
{
for (int i = 1; i < HDD_count; i++)
{
log_info("HDD[%i].name %s\n", i, HDD[i].name);
e[i].name = strdup(HDD[i].name);
}
for (int i = 1; i < ext_count; i++)
{
log_info("ext[%i].name %s\n", i, ext[i].name);
e[i + HDD_count].name = strdup(ext[i].name);
}
}
else
printf("Failed to Allocate the Array");
char tmp[256];
int i = 1, j;
for(j = 1; j <= total; j++)
{
snprintf(&tmp[0], 255, "%s", e[ j].name);
log_info("%i:%s\n", j , tmp);
}
return 0;
}
Here is a rewrite of a snippet of main() that I mentioned in my comment above:
#define CHECK(p, msg) if(!(p)) { printf("%s:%d: %s", __FILE__, __LINE__, msg); return 1;}
...
entry_t *HDD = get_item_entries("/mnt/f/n", &HDD_count);
CHECK(HDD, "HDD failed to get entries");
entry_t *ext = get_item_entries("/mnt/f/dls", &ext_count);
CHECK(ext, "ext failed to get entries");
uint32_t total = HDD_count + ext_count;
entry_t *e = malloc(total * sizeof(*e));
CHECK(e, "malloc failed");
for(int i = 0; i < HDD_count; i++) {
log_info("HDD[%i].name %s\n", i, HDD[i].name);
e[i].name = strdup(HDD[i].name);
}
// write a function instead of duplicating code?
for (int i = 0; i < ext_count; i++) {
log_info("ext[%i].name %s\n", i, ext[i].name);
e[HDD_count + i].name = strdup(ext[i].name);
}
It looks like a short lived program, but I would still free the values from strdup() and e itself.

C: Output difference between printf and vfprintf when using the exact same inputs

I'm trying to write a logger function that logs stuff to a file in a printf manner but, if enabled, it will also log to the console. I'm trying it out with a custom function that uses a custom string struct representing a number and transforms it into an actual number.
The main function:
#define MSG "0xab45cdef"
int main(){
String s;
stringInit(&s);
stringSetString(&s,MSG,sizeof(MSG));
stringPrint(&s);
logOut("\nTransforming to value\n");
int64_t v = parseValue(s);
logOut("\n");
logOut("\nResult %li \n", v);
}
My output log function
void logOut(const char *control_string, ...){
FILE *fp;
fp = fopen(LOG_OUTPUT,"ab+");
va_list argptr;
va_start(argptr,control_string);
vfprintf(fp,control_string,argptr);
#ifdef LOG_CONSOLE
printf(control_string,argptr);
#endif
va_end(argptr);
fclose(fp);
}
My String related functions
typedef struct {
char *s;
unsigned int size;
} String;
void stringInit(String *s){
s->s = NULL;
s->size = 0;
}
void stringAddChar(String *s, char c){
if (s->size > 0){
// Adding one more char.
s->s = (char *) realloc (s->s, (s->size + 1)*sizeof(char));
}
else{
// First char.
s->s = (char *) malloc(sizeof(char));
}
s->size++;
s->s[s->size-1] = c;
}
void stringFree(String *s){
if (s->size == 0) return;
free(s->s);
s->s = NULL;
s->size = 0;
}
void stringSetString(String *s, char *str, uint32_t nsize){
// Clearing the previous string.
stringFree(s);
for (uint32_t i = 0; i < nsize; i++){
// This avoids the extra char in a null terminated string.
if ((i == nsize-1) && (str[i] == 0)) break;
stringAddChar(s,str[i]);
}
}
void stringPrint(String *s){
for (uint32_t i = 0; i < s->size; i++){
logOut("%c",s->s[i]);
}
}
And finally the parseValue function
int64_t power(int64_t base, int64_t exp){
int64_t ans = 1;
for (int i = 0; i < exp; i++){
ans = ans * base;
}
return ans;
}
int64_t parseValue(String input){
int64_t base = 10;
int64_t res = 0;
int64_t maxpow = input.size-1;
uint32_t start = 0;
if (input.size > 0){
// Must check if it is hex or not.
if (input.s[0] == '0' && input.s[1] == 'x'){
base = 16;
start = 2;
maxpow = input.size-3;
}
}
for (int i = start; i < input.size; i++){
int64_t p = maxpow;
maxpow--;
char c = toupper(input.s[i]);
// printf("Char %d is %d\n",i,c);
int64_t v = c - 48;
if ((v >= 0) && (v <= 9)){
res = res + v*power(base,p);
}
else if ((c >= 65) && (c <= 70)){
if (base == 16){
v = c - 55;
res = res + v*power(base,p);
}
else{
logOut("Invalid char %c in a decimal number\n",c);
return -1;
}
}
else{
logOut("Invalid digit %d\n",c);
return -1;
}
}
return res;
}
Now when I run the main the console outputs:
pppppppppp
Transforming to value
Result 140726926743712
While my log.txt file has this
0xab45cdef
Transforming to value
Result 2873478639
The content of the log.txt file is correct. So why is the console output different?
To summarize, your logOut function has some mistakes. You can't "re-use" va_list after it has been used in another function. The function could look like this:
void logOut(const char *control_string, ...){
FILE *fp;
fp = fopen(LOG_OUTPUT,"ab+");
if (fp == NULL) {
abort();
}
va_list argptr;
va_start(argptr, control_string);
if (vfprintf(fp, control_string, argptr) < 0) {
// handle error
}
va_end(argptr);
fclose(fp);
#ifdef LOG_CONSOLE
va_start(argptr, control_string); // do not re-use va_list
vprintf(control_string, argptr);
// ^ you pass va_list
va_end(argptr);
#endif
}
Notes:
realloc(NULL, ...) is equal to malloc(...). So there is no need to if (s->size > 0){ inside stringAddChar - just call realloc(s->s, and make sure that s->s is NULL when size is zero.
Your code misses a lot of error handling. A proper way to handle realloc is to use a temporary pointer so that original memory will not leak: void *p = realloc(s->s, ...); if (p == NULL) { free(s->s); /* handle errors */ abort(); } s->s = p;.
Try not to use magic numbers in code. c - 48; is better written as c - '0';.

Loss of values in array in struct after function execution

I am working on a c code that holds a structure that hosts some values which I call range.
My purpose is to use this so called range dynamically (holding different amount of data at every execution). I am now provisionally using the # define comp instead. This so called range gets updated every time I call my update_range though the use of s1 structure (and memory allocations).
What I found weird is that when I introduced a "show_range" function to output the actual values inside/outside the update function I realized that I loose the first two values.
Here is the code.
Any suggestions on that?
Thanks in advance!
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <complex.h>
#define comp 1024
// struct holding a complex-valued range
struct range {
int dimensions; /* number of dimensions */
int* size; /* array holding number of points per dimension */
complex double* values; /* array holding complex valued */
int components; /* number of components that will change on any execution*/
};
// parameters to use in function
struct s1 {
int tag;
struct range* range;
};
int update_range(struct s1* arg);
int show_range(struct range* argrange, char* message);
int copy_range(struct range* in, struct range* out);
int main(void) {
int ret = 0;
struct s1 s1;
s1.tag = 0;
s1.range = malloc(sizeof(struct range));
update_range(&s1);
show_range(s1.range, "s1.range inside main function");
return ret;
}
////////////////////////////////////////////
int update_range(struct s1* arg) {
int ret = 0;
int i;
struct range range;
range.dimensions = 1;
range.size = malloc(range.dimensions * sizeof(int));
range.components = comp;
range.size[0] = range.components; // unidimensional case
range.values = malloc(range.components * sizeof(complex double));
for (i = 0; i < range.components; i++) {
range.values[i] = (i + 1) + I * (i + 1);
}
show_range(&range, "range inside update_range function");
arg->range->size =
malloc(range.dimensions * sizeof(int)); // size was unknown before
arg->range->values =
malloc(comp * sizeof(complex double)); // amount of values was unknown
copy_range(&range, arg->range);
show_range(arg->range, "arg->range inside update_range function");
if (range.size)
free(range.size);
range.size = NULL;
if (range.values)
free(range.values);
range.values = NULL;
return ret;
}
////////////////////////////////////////////
// Show parameters (10 first values)
int show_range(struct range* argrange, char* message) {
int ret = 0;
vint i;
printf(" ******************************\n");
printf(" range in %s \n", message);
printf(" arg.dimensions=%d \n", argrange->dimensions);
printf(" arg.size[0]=%d \n", argrange->size[0]);
printf(" argrange.components=%d \n", argrange->components);
printf(" first 10 {Re} values: \n");
for (i = 0; i < 10; i++) {
printf(" argrange.values[%d]=%f\n", i, creal(argrange->values[i]));
}
printf("\n");
return ret;
}
////////////////////////////////////////////
// copy range
int copy_range(struct range* in, struct range* out) {
int ret = 0;
if (in == NULL) {
fprintf(stderr, "error: in points to NULL (%s:%d)\n", __FILE__,
__LINE__);
ret = -1;
goto cleanup;
}
if (out == NULL) {
fprintf(stderr, "error: out points to NULL (%s:%d)\n", __FILE__,
__LINE__);
ret = -1;
goto cleanup;
}
out->dimensions = in->dimensions;
out->size = in->size;
out->values = in->values;
out->components = in->components;
cleanup:
return ret;
}
Your copy_range function is broken, because it copy only pointer to size and values and not the memory. After you call free(range.size); and free(range.values); you are deleting mamory also from original object but without setting its pointers back to NULL.
After calling update_range, s1.range has non NULL pointers in size and values, but they are pointing to deleted memory.
You are experiencing undefined behaviour (UB) due to accessing freed memory. Your copy_range() function only does a shallow copy of the two pointer fields so when you run free(range->size) you make arg->range->size invalid.
You should make copy_range() a deep copy by allocating and copying the pointer contents like:
out->size = malloc(in->dimensions * sizeof(int));
memcpy(out->size, in->size, in->dimensions * sizeof(int));
out->values = malloc(in->components * sizeof(complex double));
memcpy(out->values , in->values, in->components * sizeof(complex double));
There are not 10 items to print, so the lines:
printf(" first 10 {Re} values: \n");
for (i = 0; i < 10; i++) {
printf(" argrange.values[%d]=%f\n", i, creal(argrange->values[i]));
}
Will be printing from random memory.
a much better method would be:
printf(" first %d {Re} values: \n", min(argrange.components,10));
for (i = 0; i < argrange.components; i++) {
printf(" argrange.values[%d]=%f\n", i, creal(argrange->values[i]));
}
The above is just one of many problems with the code.
I would suggest executing the code using a debugger to get the full story.
as it is, the code has some massive memory leaks due mostly
to overlaying malloc'd memory pointers.
for instance as in the following:
arg->range->size =
malloc(range.dimensions * sizeof(int)); // size was unknown before
arg->range->values =
malloc(comp * sizeof(complex double)); // amount of values was unknown

Program running in debug mode in C::B only

I am currently writing a program which at some point needs to store the sizes of all the files in a specific folder in an array. My program crashes when running it on Code::Blocks in release mode, but does run in debug mode. It also runs without any problems on XCode on Mac OS. Here's the code:
#define MALLOC_ERROR -1
#define DIR_ACCESS_ERROR -2
#define FILE_ACCESS_ERROR -3
#define FILE_OPEN_ERROR -4
#define OUTPUT_FILE_OPEN_ERROR -5
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/stat.h>
#include <string.h>
#include <math.h>
int compare(const void*, const void*);
int getFileSizeMarginNumbers(char[], int, unsigned long int*);
int main(void){
FILE *out;
int n;
int i, count = 1;
unsigned long int *fileSizeMarginNumber;
char directoryName[] = "C:\\Users\\Борис\\Desktop\\new\\";
char outputFilePath[] = "C:\\Users\\Борис\\Desktop\\out.txt";
printf("Enter number of files in directory:");
scanf("%d", &n);
if (!(fileSizeMarginNumber = (unsigned long int*)malloc(n*sizeof(unsigned long int)))){
printf("Not enough memory");
return MALLOC_ERROR;
}
switch (getFileSizeMarginNumbers(directoryName, n, fileSizeMarginNumber)) {
case MALLOC_ERROR:
return MALLOC_ERROR;
break;
case DIR_ACCESS_ERROR:
return DIR_ACCESS_ERROR;
break;
case FILE_ACCESS_ERROR:
return FILE_ACCESS_ERROR;
break;
case FILE_OPEN_ERROR:
return FILE_OPEN_ERROR;
break;
}
qsort(fileSizeMarginNumber, n, sizeof(unsigned long int), compare);
if (!(out = fopen(outputFilePath, "w"))){
printf("Unable to open output file");
return OUTPUT_FILE_OPEN_ERROR;
}
for (i=1; i<n; ++i) {
if (fileSizeMarginNumber[i] == fileSizeMarginNumber[i - 1])
++count;
else{
fprintf(out, "%ldKB - %ldKB: %d file(s);\n", (fileSizeMarginNumber[i - 1] - 1)*16, fileSizeMarginNumber[i - 1]*16, count);
count = 1;
}
}
fprintf(out, "%ldKB - %ldKB: %d files;\n", (fileSizeMarginNumber[i - 1] - 1)*16, fileSizeMarginNumber[i - 1]*16, count);
return 0;
}
int getFileSizeMarginNumbers(char directoryName[], int n, unsigned long int *fileSizeMarginNumber){
int i, fileDescriptor;
struct dirent *file;
struct stat currentSize;
DIR *directory;
FILE *buf;
if (!(directory = opendir(directoryName))) {
printf("Unable to get access to the specified directory");
return DIR_ACCESS_ERROR;
}
for (i=0;i<2;++i){
if (!(file = readdir(directory))) {
printf("Unable to get access to a file in the specified directory");
return FILE_ACCESS_ERROR;
}
}
for (i=0; i<n; ++i) {
if (!(file = readdir(directory))) {
printf("Unable to get access to a file in the specified directory");
return FILE_ACCESS_ERROR;
}
printf("%s", strcat(strdup(directoryName), file->d_name));
if (!(buf = fopen(strcat(strdup(directoryName), file->d_name), "r"))){
printf("Unable to process a file in the specified directory");
return FILE_OPEN_ERROR;
}
fileDescriptor = fileno(buf);
fstat(fileDescriptor, &currentSize);
fileSizeMarginNumber[i] = (unsigned long int)truncl(currentSize.st_size/(1024*16)) + 1;
fclose(buf);
}
return 0;
}
int compare(const void *a, const void *b){
if ((*(long int*)a - *(long int*)b) < 0)
return -1;
else if ((*(long int*)a - *(long int*)b) == 0)
return 0;
else
return 1;
}
The problem seems to be located in the following block:
for (i=0; i<n; ++i) {
if (!(file = readdir(directory))) {
printf("Unable to get access to a file in the specified directory");
return FILE_ACCESS_ERROR;
}
printf("%s", strcat(strdup(directoryName), file->d_name));
if (!(buf = fopen(strcat(strdup(directoryName), file->d_name), "r"))){
printf("Unable to process a file in the specified directory");
return FILE_OPEN_ERROR;
}
fileDescriptor = fileno(buf);
fstat(fileDescriptor, &currentSize);
fileSizeMarginNumber[i] = (unsigned long int)truncl(currentSize.st_size/(1024*16)) + 1;
fclose(buf);
}
The program is supposed to open all files in a folder step-by-step. However, the second or third call to fopen sometimes triggers the following exception:
Program received signal SIGSEGV, Segmentation fault.
In ntdll!RtlLargeIntegerToChar () (C:\Windows\system32\ntdll.dll)
I do not understand this dependency on where I am running my program and I would've been very grateful for your help.
Thank you very much in advance.
That strcat() in fopen() is not correct:
strcat(strdup(directoryName), file->d_name)
First strdup() allocates strlen(directoryName)+1 bytes and copies directoryName then strcat() wants to append file->d_name to that copy. But as there is no extra space allocated a seg fault might occur. Do something like that instead:
char *pathname = MALLOC( strlen(directoryName) + strlen(file->d_name) + 1 );
strcpy( pathname, directoryName );
strcat( pathname, file->d_name );
And don't forget to free(pathname) when you don't need it any more

int queue with compare and swap has race condition

I have written a synchronised queue for holding integers and am faced with a weird race condition which I cannot seem to be able to understand.
Please do NOT post solutions, I know how to fix the code and make it work, I want to know what the race condition is and why it is not working as intended. Please help me understand what is going wrong and why.
First the important part of the code:
This assumes that the application will never put in more then the buffer can hold, thus no check for the current buffer size
static inline void int_queue_put_sync(struct int_queue_s * const __restrict int_queue, const long int value ) {
if (value) { // 0 values are not allowed to be put in
size_t write_offset; // holds a current copy of the array index where to put the element
for (;;) {
// retrieve up to date write_offset copy and apply power-of-two modulus
write_offset = int_queue->write_offset & int_queue->modulus;
// if that cell currently holds 0 (thus is empty)
if (!int_queue->int_container[write_offset])
// Appetmt to compare and swap the new value in
if (__sync_bool_compare_and_swap(&(int_queue->int_container[write_offset]), (long int)0, value))
// if successful then this thread was the first do do this, terminate the loop, else try again
break;
}
// increment write offset signaling other threads where the next free cell is
int_queue->write_offset++;
// doing a synchronised increment here does not fix the race condition
}
}
This seems to have a rare race condition which seems to not increment the write_offset.
Tested on OS X gcc 4.2, Intel Core i5 quadcore and Linux Intel C Compiler 12 on RedHat 2.6.32 Intel(R) Xeon(R). Both produce race conditions.
Full source with test cases:
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>
// #include "int_queue.h"
#include <stddef.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#ifndef INT_QUEUE_H
#define INT_QUEUE_H
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
struct int_queue_s {
size_t size;
size_t modulus;
volatile size_t read_offset;
volatile size_t write_offset;
volatile long int int_container[0];
};
static inline void int_queue_put(struct int_queue_s * const __restrict int_queue, const long int value ) {
if (value) {
int_queue->int_container[int_queue->write_offset & int_queue->modulus] = value;
int_queue->write_offset++;
}
}
static inline void int_queue_put_sync(struct int_queue_s * const __restrict int_queue, const long int value ) {
if (value) {
size_t write_offset;
for (;;) {
write_offset = int_queue->write_offset & int_queue->modulus;
if (!int_queue->int_container[write_offset])
if (__sync_bool_compare_and_swap(&(int_queue->int_container[write_offset]), (long int)0, value))
break;
}
int_queue->write_offset++;
}
}
static inline long int int_queue_get(struct int_queue_s * const __restrict int_queue) {
size_t read_offset = int_queue->read_offset & int_queue->modulus;
if (int_queue->write_offset != int_queue->read_offset) {
const long int value = int_queue->int_container[read_offset];
int_queue->int_container[read_offset] = 0;
int_queue->read_offset++;
return value;
} else
return 0;
}
static inline long int int_queue_get_sync(struct int_queue_s * const __restrict int_queue) {
size_t read_offset;
long int volatile value;
for (;;) {
read_offset = int_queue->read_offset;
if (int_queue->write_offset == read_offset)
return 0;
read_offset &= int_queue->modulus;
value = int_queue->int_container[read_offset];
if (value)
if (__sync_bool_compare_and_swap(&(int_queue->int_container[read_offset]), (long int)value, (long int)0))
break;
}
int_queue->read_offset++;
return value;
}
static inline struct int_queue_s * int_queue_create(size_t num_values) {
struct int_queue_s * int_queue;
size_t modulus;
size_t temp = num_values + 1;
do {
modulus = temp;
temp--;
temp &= modulus;
} while (temp);
modulus <<= 1;
size_t int_queue_mem = sizeof(*int_queue) + ( sizeof(int_queue->int_container[0]) * modulus);
if (int_queue_mem % sysconf(_SC_PAGE_SIZE)) int_queue_mem += sysconf(_SC_PAGE_SIZE) - (int_queue_mem % sysconf(_SC_PAGE_SIZE));
int_queue = mmap(NULL, int_queue_mem, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE , -1, 0);
if (int_queue == MAP_FAILED)
return NULL;
int_queue->modulus = modulus-1;
int_queue->read_offset = 0;
int_queue->write_offset = 0;
int_queue->size = num_values;
memset((void*)int_queue->int_container, 0, sizeof(int_queue->int_container[0]) * modulus);
size_t i;
for (i = 0; i < num_values; ) {
int_queue_put(int_queue, ++i );
}
return int_queue;
}
#endif
void * test_int_queue_thread(struct int_queue_s * int_queue) {
long int value;
size_t i;
for (i = 0; i < 10000000; i++) {
int waited = -1;
do {
value = int_queue_get_sync(int_queue);
waited++;
} while (!value);
if (waited > 0) {
printf("waited %d cycles to get a new value\n", waited);
// continue;
}
// else {
printf("thread %p got value %ld, i = %zu\n", (void *)pthread_self(), value, i);
// }
int timesleep = rand();
timesleep &= 0xFFF;
usleep(timesleep);
int_queue_put_sync(int_queue, value);
printf("thread %p put value %ld back, i = %zu\n", (void *)pthread_self(), value, i);
}
return NULL;
}
int main(int argc, char ** argv) {
struct int_queue_s * int_queue = int_queue_create(2);
if (!int_queue) {
fprintf(stderr, "error initializing int_queue\n");
return -1;
}
srand(0);
long int value[100];
size_t i;
for (i = 0; i < 100; i++) {
value[0] = int_queue_get(int_queue);
if (!value[0]) {
printf("error getting value\n");
}
else {
printf("got value %ld\n", value[0]);
}
int_queue_put(int_queue, value[0]);
printf("put value %ld back successfully\n", value[0]);
}
pthread_t threads[100];
for (i = 0; i < 4; i++) {
pthread_create(threads + i, NULL, (void * (*)(void *))test_int_queue_thread, int_queue);
}
for (i = 0; i < 4; i++) {
pthread_join(threads[i], NULL);
}
return 0;
}
Interesting question. Here is a wild guess. :-)
It seems you need some synchronization between your read_offset and write_offset.
For example, here is a race that may be related or not. Between your compare-and-swap and the write_offset increment you may have a reader come in and set the value back to zero.
Writer-1: get write_offset=0
Writer-2: get write_offset=0
Writer-1: compare-and-swap at offset=0
Writer-1: Set write_offset=1
Reader-1: compare-and-swap at offset=0 (sets it back to zero)
Writer-2: compare-and-swap at offset=0 again even though write_offset=1
Writer-2: Set write_offset=2
I believe that int_queue->write_offset++; is the problem: if two threads execute this instruction simultaneously, they will both load the same value from memory, increment it, and store the same result back (such that the variable only increases by one).
my opinion is
int_queue->write_offset++;
and
write_offset = int_queue->write_offset & int_queue->modulus;
are not thread safe

Resources