Operacje bitowe

Implementacja operacji bitowych - tych, których nie oferuje C - została umieszczona w źródłach jądra Linuxa w pliku include/asm-i386/bitops.h. Niektóre z tych operacji zaimplementowane są jako atomowe dla SMP. Efekt ten został uzyskany przez zastosowanie makra LOCK_PREFIX - jego definicja wygląda identycznie jak w przypadku makra LOCK z pliku atomic.h:

#ifdef CONFIG_SMP
#define LOCK_PREFIX "lock ; "
#else
#define LOCK_PREFIX ""
#endif

Funkcja set_bit atomowo ustawia dany bit w zmiennej pod danym adresem.

static __inline__ void set_bit(int nr, volatile void * addr)
{
	__asm__ __volatile__( LOCK_PREFIX
		"btsl %1,%0"
		:"=m" (ADDR)
		:"Ir" (nr));
}

Jak widać wszystko sprowadza się do użycia jednej instrukcji asemblera, która robi dokładnie to, czego nam potrzeba. Ciekawe jest to, że instrukcja asemblerowa btsl nie wymaga, aby pierwszy argument był przekazywany przez rejestr, natomiast set_bit(), jak widać, wymaga.

Jeszcze dwa podobne przykłady; funkcja __change_bit nie jest atomowa w SMP, ale oczywiście posiada niepodzielny odpowiednik.

static __inline__ void clear_bit(int nr, volatile void * addr)
{
	__asm__ __volatile__( LOCK_PREFIX
		"btrl %1,%0"
		:"=m" (ADDR)
		:"Ir" (nr));
}
static __inline__ void __change_bit(int nr, volatile void * addr)
{
	__asm__ __volatile__(
		"btcl %1,%0"
		:"=m" (ADDR)
		:"Ir" (nr));
}

static __inline__ unsigned long ffz(unsigned long word)
{
	__asm__("bsfl %1,%0"
		:"=r" (word)
		:"r" (~word));
	return word;
}

Funkcja ffz jest niepodzielna (róznież w SMP), ponieważ składa się z jednej instrukcji asemblera i nie korzysta z pamięci.

Oprócz tych, są dostępne równie proste funkcje __set_bit, __clear_bit, __test_and_set_bit, __test_and_clear_bit, __test_and_change_bit - w SMP podzielne, oraz change_bit, test_and_set_bit, test_and_clear_bit, test_and_change_bit - w SMP atomowe.

Są też operacje bardziej złożone, na przykład znajdowanie pierwszego bitu zerowego w zadanym obszarze pamięci (zwracany jest numer bitu, a nie bajtu).

static __inline__ int find_first_zero_bit(void * addr, unsigned size)
{
	int d0, d1, d2;
	int res;

	if (!size)
		return 0;
	/* This looks at memory. Mark it volatile to tell gcc not to move it around */
	__asm__ __volatile__(
		"movl $-1,%%eax\n\t"
		"xorl %%edx,%%edx\n\t"
		"repe; scasl\n\t"
		"je 1f\n\t"
		"xorl -4(%%edi),%%eax\n\t"
		"subl $4,%%edi\n\t"
		"bsfl %%eax,%%edx\n"
		"1:\tsubl %%ebx,%%edi\n\t"
		"shll $3,%%edi\n\t"
		"addl %%edi,%%edx"
		:"=d" (res), "=&c" (d0), "=&D" (d1), "=&a" (d2)
		:"1" ((size + 31) >> 5), "2" (addr), "b" (addr));
	return res;
}

Zapisanie tej funkcji w C spowodowałoby wygenerowanie w najleprzym wypadku kodu o tej samej efektywności, jednak kompilator jezyka wysokiego poziomu nie może nam zapewnić zawsze takiej wydajności.

W pliku bitops.h zdefiniowana jest jeszcze podobna funkcja: find_first_zero_bit.