Loading...
dZf1aeA-rsmarinoff avatar dZf1aeA-rsmarinoff 49 Точки

Лекция 6 - Домашно - Задача 2

Първоначално задачата не ми направи впечатление, тъй като решението изглеждаше сравнително лесно, но после ударих на камък. За да сметна някакъв триъгълник ми трябват операции с плаваща точка, от рода на корени, тригонометрия и т.н. Ядрото (очаквано) не харесва особено операциите с плаваща точка по ред причини, които не са важни в случая, затова и няма кой знае какви библиотеки за тях, поне аз не можах да намеря, а по форумите като цяло се карат на всеки, който пита за fp аритметика и се чудят за какво му е. laugh Тук се сетих за два варианта да заобиколя въпросния проблем:

Самата floating point аритметика може да бъде пусната за x86 архитектури по следния начин:

#include <asm/i387.h>

int some_function(void){
    kernel_fpu_begin();
// Do some calculations or whatever...
    kernel_fpu_end();
    return 0;
}

 Програмата ще използва копроцесора за аритметика с плаваща запетая и ще направи каквото трябва. Между другото, имаше и някакъв друг хедър, който емулираше тази аритметика софтуерно, така че и той остава вариант, както и да е. Това означава, обаче, да си пиша сам функцията за корен квадратен, което не е чак такъв проблем. Тригонометрията вероятно ще е голям зор, ако въобще е възможно да се напише без inline assembly в нея. Отново, леко задънена улица, или поне нещо, което предпочитам да избегна.

Вторият вариант е да използвам inline assembly и по подобен начин на горния да си направя сметките с x87 инструкциите, където има и корен квадратен, и тригонометрия. Назад съм с асемблера, но, като цяло, приемлив вариант. Тук, обаче, също удрям на камък, защото не мога да намеря функция, която ще изпринти floating point стойност в proc файл. seq_printf() отказва упорито.

С това се изчерпва моят опит до момента, споделете и вашия, ако искате, разбира се.

1
Linux Курсове 08/02/2016 01:31:00
vladiant avatar vladiant 41 Точки

И да споделя малко опит. Може да се шокирате, но е напълно възможно един и същ бинарен код на различни машини да даде различни резултати при пресмятания с плаваща запетая. Разликите започват от LSB и може напълно да обезсмислят резултата в зависимост от типа на пресмятането. Затова е желателно максимално да избягвате пресмятания с плаваща запетая - още повече, че са и по-бавни. За справка:

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Още информация:

http://javarevisited.blogspot.bg/2014/05/10-articles-every-programmer-must-read.html

1
dZf1aeA-rsmarinoff avatar dZf1aeA-rsmarinoff 49 Точки

Мога да добавя малко лош опит. Продължих да се лигавя с floating point аритметика, въпреки добрата си преценка. Нещата работят, но само донякъде. Освен, че асемблерските инструкции ограничават до използването на определена архитектура, но и както е споменато горе, floating point аритметиката може да доведе до undefined behavior. Описал съм всичко в коментарите на постнатия код, но като цяло наистина се случват някои странни работи. Примерно, когато извикам функцията sqroot върху някой литерал и всичко е наред. Викна ли я обаче върху някакъв друг тип, дори и кастнат към float, всичко приключва и компилаторът хвърля warning за undefined behavior и модулът не се зарежда, или прави segmentation fault-ове и други подобни глупости. За това намерих workaround, като написах функция за преобразуване на int в някаква форма на float. Когато викна sqroot върху някакъв сбор от променливи, се случва нещо подобно и т.н. Като цяло не си заслужава мъките и писането на откровени простотии, затова отивам да си смятам векторите. Поствам кода кат предупреждение за ентусиасти като мен. laugh

/*
 * This one is just for kicks and it kinda demonstrates
 * why you shoudln't use floating point arithmetic in
 * a kernel module. It basically results in A LOT of
 * undefined behaviour, for which there is probably
 * a workaround but it's not worth the psychological
 * disorders on might develop looking for it.
 * Kernel modules rarely ever need to use fp arithmetic
 * in general anyway. WARNING! This will crash, if you load it
 * ... badly. So, use at your own risk
 */

#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <asm/i387.h>

/***Typedefs prototypes, etc.***/
/*
 * This union is used to convert floats
 * into some form of int representation
 * so that they may later be printed,
 * since the print functions don't support
 * the %f format specifier.
 * Suffice to say, not my best idea as of
 * yet.
*/
typedef union{
	float f;
	struct{
		unsigned int mantisa : 23;
		unsigned int exponent : 8;
		unsigned int sign : 1;
	}parts;
}NEWFLOAT;

/*
 * Printable version of a float number.
 * I use this one to print the actual
 * floats after converting them from the
 * NEWFLOAT type.
 */
typedef struct{
	char sign;
	unsigned int exponent;
	unsigned int mantisa;
}PRINTFLOAT;

int power(int, int);
NEWFLOAT intToFloat(int);
PRINTFLOAT convertFloat(NEWFLOAT number);
static float sqroot(float);
float sin(float);
float cos(float);
/*******Global variables*******/
static NEWFLOAT side1, side2, side3;
static PRINTFLOAT a, b, c;
static int x1, x2, x3, y1, y2, y3;
module_param(x1, int, 0);
module_param(x2, int, 0);
module_param(x3, int, 0);
module_param(y1, int, 0);
module_param(y2, int, 0);
module_param(y3, int, 0);
/*
 * Converts an integer value
 * into a NEWFLOAT value. I
 * wrote this, because casting
 * an int to float and passing it
 * to one of the trig functions below
 * resulted in a lot of undefind behavior.
 * This is actually a passable workaround
 * for the issue.
 */
NEWFLOAT intToFloat(int arg){
	NEWFLOAT returnValue;
	unsigned int localArg = 0;
	unsigned int mask = 0x80000000;
	int i = 0;
	if(arg < 0){
		localArg |= -1*arg;
		returnValue.parts.sign = 1;
	}else{
		localArg |= arg;
		returnValue.parts.sign = 0;
	}
	for(i=32;i>0;i--){
		if((localArg & mask) != 0){
			break;
		}
		mask >>=1;
	}
	returnValue.parts.mantisa = (localArg & (~mask));
	returnValue.parts.mantisa <<= 24-i;
	returnValue.parts.exponent = i + 126;
	return returnValue;
}
/*
 * Uses the x87 sqrt instruction to get
 * the square root of a float value.
 * Calling this on the sum of two floats
 * resulted in undefined behavior at
 * which point I called it quits.
 */
static float sqroot(float arg){
	float localArg = arg;
	float returnValue;
	asm(
		"fld %1\n;"
		"fsqrt\n;"
		"fst %0;"
	:"=m"(returnValue)
	: "m"(localArg)
	);
	return returnValue;
}

/*
 * Same as above, only for sine.
 */
float sin(float arg){
	float returnValue;
	asm(
		"fld %1\n;"
		"fsin\n;"
		"fst %0;"
		:"=m"(returnValue)
		: "m"(arg)
	);
	return returnValue;
}

/*
 * Same as above, only for cosine.
 */
float cos(float arg){
	float returnValue;
	asm(
		"fld %1\n;"
		"fcos\n;"
		"fst %0;"
		:"=m"(returnValue)
		:"m"(arg)
	);
	return returnValue;
}

/*
 * Converts the NEWFLOAT union int a
 * format ready for printing. Basically
 * calculates the decimal values of the
 * exponent and mantissa from the IEE
 * float format.
 */
PRINTFLOAT convertFloat(NEWFLOAT number){
	int i=0;
	unsigned int increment = 500000;
	unsigned int mask = 0x00400000;
	unsigned int result = 1000000;
	unsigned int afterBias = 0;
	PRINTFLOAT returnValue;
	for(i=0;i<23;i++){
		if((number.parts.mantisa & mask) !=0){
			result += increment;
		}
		increment /= 2;
		mask >>= 1;
	}
	if(number.parts.exponent < 127){
		afterBias = 127 - number.parts.exponent;
		result /= power(2, afterBias);
	}else{
		afterBias = number.parts.exponent - 127;
		result*=power(2, afterBias);
	}

	if(number.parts.sign !=0){
		returnValue.sign = '-';
	}else{
		returnValue.sign = ' ';
	}
	returnValue.exponent = result/1000000;
	returnValue.mantisa = result%1000000;
	return returnValue;
}

/*
 * Power function, pretty
 * self-explanatory.
 */
int power(int number, int power){

	int result = 1;
	int i = 0;
	for(i=0;i<power;i++){
		result *= number;
	}
	return result;
}

static int hello_proc_show(struct seq_file *m, void *v) {
	seq_printf(m, "Side a: %c\n%u\n%u\n", a.sign, a.exponent, a.mantisa);
 	seq_printf(m, "Side b:%c%u.%u\n", b.sign, b.exponent, b.mantisa);
	seq_printf(m, "Side c:%c%u.%u\n", c.sign, c.exponent, c.mantisa);
  	return 0;
}

static int hello_proc_open(struct inode *inode, struct  file *file) {
  	return single_open(file, hello_proc_show, NULL);
}

static const struct file_operations hello_proc_fops = {
	.owner = THIS_MODULE,
  	.open = hello_proc_open,
  	.read = seq_read,
  	.llseek = seq_lseek,
  	.release = single_release,
};

static int __init hello_proc_init(void) {
  	kernel_fpu_begin();
	NEWFLOAT foo = intToFloat(power(x2-x1, 2));
	NEWFLOAT bar = intToFloat(power(y2-y1, 2));
	NEWFLOAT thingy;
	thingy.f = foo.f + bar.f;
	side1.f = sqroot(thingy.f);
	a = convertFloat(side1);
	side2.f = sqroot(power(x3-x2, 2)+power(y3-y2, 2));
	side3.f = sqroot(power(x1-x3, 2)+power(y1-y3, 2));
	a = convertFloat(side1);
	b = convertFloat(side2);
	c = convertFloat(side3);
	kernel_fpu_end();
  	proc_create("hello_proc", 0, NULL, &hello_proc_fops);
  	return 0;
}

static void __exit hello_proc_exit(void) {
  	remove_proc_entry("hello_proc", NULL);
}

MODULE_LICENSE("GPL");
module_init(hello_proc_init);
module_exit(hello_proc_exit);

 

1
09/02/2016 23:05:39
vladiant avatar vladiant 41 Точки

Благодаря за кода и информацията. За сведение - в MM Solutions само на няколко човека е разрешено да пишат на асемблер за нуждите на клиентите.

0
dZf1aeA-rsmarinoff avatar dZf1aeA-rsmarinoff 49 Точки

Асемблерът е нещо много хубаво, но поставя някои ограничения. Като, цяло, ако си напред с математиката можеш да пишеш много оптимален код, което е в пъти по-добре от C компилатор с набичена на макс оптимизация в който и да е случай. Уловката идва тогава, когато проектът е голям и трябва да пише повече от един човек и се стигне до разпъване и рисуване по чаршафи, спане на работа и т.н. Не е за общи цели.

0
agogo avatar agogo 12 Точки

А дали няма да е лошо да се направи един курс и за Асемблер?

Какво мислите за идеята?

-1
dZf1aeA-rsmarinoff avatar dZf1aeA-rsmarinoff 49 Точки

По принцип цялата идея на операционните системи е да ти отделят хардуера от имплементацията, т.е. един и същи код да работи на различен хардуер. Асемблера, като цяло, е стъпка в обратната посока, т.е. е задраво хардуерно зависим в доста случаи и ограничава нещата. Може да се използва без проблем за устройства без операционна система, примерно на мен ми се е налагало да го ползвам като съм писал за PIC микроконтролери, въпреки че ползвам е силно казано в случая за нивото ми на разбиране. Курс за x86 асемблер, или пък за ARM асемблер би представлявал интерес за мен, въпреки че там се отдалечаваме възможно най-много от софт частта на софтуера. :D

1
agogo avatar agogo 12 Точки

Съгласен!

Но не е ли странно как тези "високотехнологични" n-ядрени процесори, обременени с "революционни" операционни системи и "висококачествен" код се "влачат" така!

А процесорчета на 30 МHz управляват производствени линии!

Но, все пак, масовите съвременни технологии са най-вече за забавление, така - всичко е разбираемо :)

Лека и успех!

0
15/02/2016 09:08:25
vladiant avatar vladiant 41 Точки

За начало - писането на многонишков код никак не е лесно. Още по-трудно е паралелизирането на алгоритмите - дори в някои случаи това е невъзможно. След това алгоритмите трябва да бъдат написано бързо, за да бъдат пуснати на пазара - дори и с цената на някои бъгове. Оптимизацията отнема време, а и не може да стане за всички системи едновременно.

От друга страна не е зле да се знае, че C се нарича още и преносим асемблер. Модерните компилатори в повечето случаи генерират достатъчно оптимален код, така че знанието на асемблер не е наложително. Но е поучително да се разгледа генерираният код :)  

1
hanasd avatar hanasd 3 Точки

Абсолютно е така!!!

Навремето една и съща програма даваше дори различни резултати под различните компилатори (не че сега не се случват такива неща понякога)

0
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.